Merge "Launch freeform activities in desktop mode" into main
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 02e8eec..e680103 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -262,6 +262,7 @@
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/InProcessTethering)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/OsuLogin)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system_other/system/app/OsuLogin)
+$(call add-clean-step, rm -rf $(OUT_DIR)/host/linux-x86/testcases/ravenwood-runtime)
 # ******************************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
 # ******************************************************************
diff --git a/DREAM_MANAGER_OWNERS b/DREAM_MANAGER_OWNERS
new file mode 100644
index 0000000..48bde60
--- /dev/null
+++ b/DREAM_MANAGER_OWNERS
@@ -0,0 +1 @@
+brycelee@google.com
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index e7adf20..f6213b9 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -31,6 +31,7 @@
         "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
 
     srcs: [
+        ":aconfigd_protos",
         ":ipconnectivity-proto-src",
         ":libstats_atom_enum_protos",
         ":libstats_atom_message_protos",
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 3ab0934..412f2b7 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -30,7 +30,7 @@
     name: "framework-minus-apex.ravenwood-base",
     tools: ["hoststubgen"],
     cmd: "$(location hoststubgen) " +
-        "@$(location ravenwood/texts/ravenwood-standard-options.txt) " +
+        "@$(location :ravenwood-standard-options) " +
 
         "--debug-log $(location hoststubgen_framework-minus-apex.log) " +
         "--stats-file $(location hoststubgen_framework-minus-apex_stats.csv) " +
@@ -42,13 +42,13 @@
         "--gen-input-dump-file $(location hoststubgen_dump.txt) " +
 
         "--in-jar $(location :framework-minus-apex-for-hoststubgen) " +
-        "--policy-override-file $(location ravenwood/texts/framework-minus-apex-ravenwood-policies.txt) " +
-        "--annotation-allowed-classes-file $(location ravenwood/texts/ravenwood-annotation-allowed-classes.txt) ",
+        "--policy-override-file $(location :ravenwood-framework-policies) " +
+        "--annotation-allowed-classes-file $(location :ravenwood-annotation-allowed-classes) ",
     srcs: [
         ":framework-minus-apex-for-hoststubgen",
-        "ravenwood/texts/framework-minus-apex-ravenwood-policies.txt",
-        "ravenwood/texts/ravenwood-standard-options.txt",
-        "ravenwood/texts/ravenwood-annotation-allowed-classes.txt",
+        ":ravenwood-framework-policies",
+        ":ravenwood-standard-options",
+        ":ravenwood-annotation-allowed-classes",
     ],
     out: [
         "ravenwood.jar",
@@ -104,6 +104,18 @@
     ],
 }
 
+genrule {
+    name: "framework-minus-apex.ravenwood.keep_all",
+    defaults: ["ravenwood-internal-only-visibility-genrule"],
+    cmd: "cp $(in) $(out)",
+    srcs: [
+        ":framework-minus-apex.ravenwood-base{hoststubgen_keep_all.txt}",
+    ],
+    out: [
+        "hoststubgen_framework-minus-apex_keep_all.txt",
+    ],
+}
+
 java_library {
     name: "services.core-for-hoststubgen",
     installable: false, // host only jar.
@@ -118,7 +130,7 @@
     name: "services.core.ravenwood-base",
     tools: ["hoststubgen"],
     cmd: "$(location hoststubgen) " +
-        "@$(location ravenwood/texts/ravenwood-standard-options.txt) " +
+        "@$(location :ravenwood-standard-options) " +
 
         "--debug-log $(location hoststubgen_services.core.log) " +
         "--stats-file $(location hoststubgen_services.core_stats.csv) " +
@@ -130,13 +142,13 @@
         "--gen-input-dump-file $(location hoststubgen_dump.txt) " +
 
         "--in-jar $(location :services.core-for-hoststubgen) " +
-        "--policy-override-file $(location ravenwood/texts/services.core-ravenwood-policies.txt) " +
-        "--annotation-allowed-classes-file $(location ravenwood/texts/ravenwood-annotation-allowed-classes.txt) ",
+        "--policy-override-file $(location :ravenwood-services-policies) " +
+        "--annotation-allowed-classes-file $(location :ravenwood-annotation-allowed-classes) ",
     srcs: [
         ":services.core-for-hoststubgen",
-        "ravenwood/texts/services.core-ravenwood-policies.txt",
-        "ravenwood/texts/ravenwood-standard-options.txt",
-        "ravenwood/texts/ravenwood-annotation-allowed-classes.txt",
+        ":ravenwood-services-policies",
+        ":ravenwood-standard-options",
+        ":ravenwood-annotation-allowed-classes",
     ],
     out: [
         "ravenwood.jar",
@@ -189,6 +201,18 @@
     ],
 }
 
+genrule {
+    name: "services.core.ravenwood.keep_all",
+    defaults: ["ravenwood-internal-only-visibility-genrule"],
+    cmd: "cp $(in) $(out)",
+    srcs: [
+        ":services.core.ravenwood-base{hoststubgen_keep_all.txt}",
+    ],
+    out: [
+        "hoststubgen_services.core_keep_all.txt",
+    ],
+}
+
 java_library {
     name: "services.core.ravenwood-jarjar",
     installable: false,
@@ -246,12 +270,20 @@
     visibility: ["//visibility:private"],
 }
 
+java_genrule {
+    name: "z00-all-updatable-modules-system-stubs",
+    cmd: "cp $(in) $(out)",
+    srcs: [":all-updatable-modules-system-stubs"],
+    out: ["z00-all-updatable-modules-system-stubs.jar"],
+    visibility: ["//visibility:private"],
+}
+
 android_ravenwood_libgroup {
     name: "ravenwood-runtime",
     libs: [
         "100-framework-minus-apex.ravenwood",
         "200-kxml2-android",
-        "all-updatable-modules-system-stubs",
+
         "android.test.mock.ravenwood",
         "ravenwood-helper-runtime",
         "hoststubgen-helper-runtime.ravenwood",
@@ -267,6 +299,9 @@
         "ravenwood-junit-impl-flag",
         "mockito-ravenwood-prebuilt",
         "inline-mockito-ravenwood-prebuilt",
+
+        // It's a stub, so it should be towards the end.
+        "z00-all-updatable-modules-system-stubs",
     ],
     jni_libs: [
         "libandroid_runtime",
diff --git a/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTestGen.py b/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTestGen.py
index eea3b84..373355a 100755
--- a/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTestGen.py
+++ b/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTestGen.py
@@ -61,8 +61,8 @@
 imports = """
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
@@ -118,4 +118,4 @@
         print("        default void f{}() {{}}".format(i*imt_size + j))
     print("    }")
 
-print("}")
\ No newline at end of file
+print("}")
diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/generate_java.py b/apct-tests/perftests/core/src/android/libcore/varhandles/generate_java.py
index f3a1fff..01abdb6 100755
--- a/apct-tests/perftests/core/src/android/libcore/varhandles/generate_java.py
+++ b/apct-tests/perftests/core/src/android/libcore/varhandles/generate_java.py
@@ -160,8 +160,8 @@
 
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-import android.test.suitebuilder.annotation.LargeTest;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
index a4a2e80..9b0f5c9 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
@@ -133,7 +133,7 @@
         pw.println("      --tag: Tag of the blob to delete.");
         pw.println("idle-maintenance");
         pw.println("    Run idle maintenance which takes care of removing stale data.");
-        pw.println("query-blob-existence [-b BLOB_ID]");
+        pw.println("query-blob-existence [-b BLOB_ID] [-u | --user USER_ID]");
         pw.println("    Prints 1 if blob exists, otherwise 0.");
         pw.println();
     }
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index c74c48c..5f57c39 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -31,6 +31,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.app.Notification;
 import android.compat.Compatibility;
 import android.compat.annotation.ChangeId;
@@ -1366,6 +1367,7 @@
          * @return This object for method chaining
          */
         @FlaggedApi(Flags.FLAG_JOB_DEBUG_INFO_APIS)
+        @SuppressLint("BuilderSetStyle")
         @NonNull
         public Builder removeDebugTag(@NonNull String tag) {
             mDebugTags.remove(tag);
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index d59d430..ad54cd39 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -491,8 +491,10 @@
      * Returns a list of all currently-executing jobs.
      * @hide
      */
-    @SuppressWarnings("HiddenAbstractMethod")
-    public abstract List<JobInfo> getStartedJobs();
+    @Nullable
+    public List<JobInfo> getStartedJobs() {
+        return null;
+    }
 
     /**
      * <b>For internal system callers only!</b>
@@ -501,8 +503,10 @@
      * <p class="note">This is a slow operation, so it should be called sparingly.
      * @hide
      */
-    @SuppressWarnings("HiddenAbstractMethod")
-    public abstract List<JobSnapshot> getAllJobSnapshots();
+    @Nullable
+    public List<JobSnapshot> getAllJobSnapshots() {
+        return null;
+    }
 
     /**
      * @hide
@@ -510,8 +514,8 @@
     @RequiresPermission(allOf = {
             android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
             android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
-    @SuppressWarnings("HiddenAbstractMethod")
-    public abstract void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer);
+    public void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
+    }
 
     /**
      * @hide
@@ -519,9 +523,10 @@
     @RequiresPermission(allOf = {
             android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
             android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
-    @SuppressWarnings("HiddenAbstractMethod")
-    public abstract void unregisterUserVisibleJobObserver(
-            @NonNull IUserVisibleJobObserver observer);
+    public void unregisterUserVisibleJobObserver(
+            @NonNull IUserVisibleJobObserver observer) {
+
+    }
 
     /**
      * @hide
@@ -529,7 +534,7 @@
     @RequiresPermission(allOf = {
             android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
             android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
-    @SuppressWarnings("HiddenAbstractMethod")
-    public abstract void notePendingUserRequestedAppStop(@NonNull String packageName, int userId,
-            @Nullable String debugReason);
+    public void notePendingUserRequestedAppStop(@NonNull String packageName, int userId,
+            @Nullable String debugReason) {
+    }
 }
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index e20f525..e489c1a 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -38,3 +38,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+   name: "thermal_restrictions_to_fgs_jobs"
+   namespace: "backstage_power"
+   description: "Apply thermal restrictions to FGS jobs."
+   bug: "315157163"
+   metadata {
+       purpose: PURPOSE_BUGFIX
+   }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 3bb395f..ba8e3e8 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1375,8 +1375,10 @@
             final JobServiceContext jsc = mActiveServices.get(i);
             final JobStatus jobStatus = jsc.getRunningJobLocked();
 
-            if (jobStatus != null && !jsc.isWithinExecutionGuaranteeTime()
-                    && restriction.isJobRestricted(jobStatus)) {
+            if (jobStatus != null
+                    && !jsc.isWithinExecutionGuaranteeTime()
+                    && restriction.isJobRestricted(
+                            jobStatus, mService.evaluateJobBiasLocked(jobStatus))) {
                 jsc.cancelExecutingJobLocked(restriction.getStopReason(),
                         restriction.getInternalReason(),
                         JobParameters.getInternalReasonCodeDescription(
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 5d1433c..ff73a49 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -80,6 +80,7 @@
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.os.storage.StorageManagerInternal;
@@ -179,6 +180,8 @@
     public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     public static final boolean DEBUG_STANDBY = DEBUG || false;
 
+    public static final String TRACE_TRACK_NAME = "JobScheduler";
+
     /** The maximum number of jobs that we allow an app to schedule */
     private static final int MAX_JOBS_PER_APP = 150;
     /** The number of the most recently completed jobs to keep track of for debugging purposes. */
@@ -310,7 +313,8 @@
      * Note: do not add to or remove from this list at runtime except in the constructor, because we
      * do not synchronize access to this list.
      */
-    private final List<JobRestriction> mJobRestrictions;
+    @VisibleForTesting
+    final List<JobRestriction> mJobRestrictions;
 
     @GuardedBy("mLock")
     @VisibleForTesting
@@ -3498,8 +3502,6 @@
 
     /**
      * Check if a job is restricted by any of the declared {@link JobRestriction JobRestrictions}.
-     * Note, that the jobs with {@link JobInfo#BIAS_FOREGROUND_SERVICE} bias or higher may not
-     * be restricted, thus we won't even perform the check, but simply return null early.
      *
      * @param job to be checked
      * @return the first {@link JobRestriction} restricting the given job that has been found; null
@@ -3508,13 +3510,9 @@
      */
     @GuardedBy("mLock")
     JobRestriction checkIfRestricted(JobStatus job) {
-        if (evaluateJobBiasLocked(job) >= JobInfo.BIAS_FOREGROUND_SERVICE) {
-            // Jobs with BIAS_FOREGROUND_SERVICE or higher should not be restricted
-            return null;
-        }
         for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
             final JobRestriction restriction = mJobRestrictions.get(i);
-            if (restriction.isJobRestricted(job)) {
+            if (restriction.isJobRestricted(job, evaluateJobBiasLocked(job))) {
                 return restriction;
             }
         }
@@ -4221,6 +4219,7 @@
         return curBias;
     }
 
+    /** Gets and returns the adjusted Job Bias **/
     int evaluateJobBiasLocked(JobStatus job) {
         int bias = job.getBias();
         if (bias >= JobInfo.BIAS_BOUND_FOREGROUND_SERVICE) {
@@ -4348,7 +4347,11 @@
 
                 final boolean wasConsideredCharging = isConsideredCharging();
                 mChargingPolicy = newPolicy;
-
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+                    Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER,
+                            JobSchedulerService.TRACE_TRACK_NAME,
+                            "CHARGING POLICY CHANGED#" + mChargingPolicy);
+                }
                 if (isConsideredCharging() != wasConsideredCharging) {
                     for (int c = mControllers.size() - 1; c >= 0; --c) {
                         mControllers.get(c).onBatteryStateChangedLocked();
@@ -5907,7 +5910,7 @@
                     if (isRestricted) {
                         for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
                             final JobRestriction restriction = mJobRestrictions.get(i);
-                            if (restriction.isJobRestricted(job)) {
+                            if (restriction.isJobRestricted(job, evaluateJobBiasLocked(job))) {
                                 final int reason = restriction.getInternalReason();
                                 pw.print(" ");
                                 pw.print(JobParameters.getInternalReasonCodeDescription(reason));
@@ -6240,7 +6243,7 @@
                         proto.write(JobSchedulerServiceDumpProto.JobRestriction.REASON,
                                 restriction.getInternalReason());
                         proto.write(JobSchedulerServiceDumpProto.JobRestriction.IS_RESTRICTING,
-                                restriction.isJobRestricted(job));
+                                restriction.isJobRestricted(job, evaluateJobBiasLocked(job)));
                         proto.end(restrictionsToken);
                     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 39d50f5..d65a66c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -535,29 +535,17 @@
             sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
             final String sourcePackage = job.getSourcePackageName();
             if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
-                final String componentPackage = job.getServiceComponent().getPackageName();
-                String traceTag = "*job*<" + job.getSourceUid() + ">" + sourcePackage;
-                if (!sourcePackage.equals(componentPackage)) {
-                    traceTag += ":" + componentPackage;
-                }
-                traceTag += "/" + job.getServiceComponent().getShortClassName();
-                if (!componentPackage.equals(job.serviceProcessName)) {
-                    traceTag += "$" + job.serviceProcessName;
-                }
-                if (job.getNamespace() != null) {
-                    traceTag += "@" + job.getNamespace();
-                }
-                traceTag += "#" + job.getJobId();
-
                 // Use the context's ID to distinguish traces since there'll only be one job
                 // running per context.
-                Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
-                        traceTag, getId());
+                Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
+                        JobSchedulerService.TRACE_TRACK_NAME, job.computeSystemTraceTag(),
+                        getId());
             }
             if (job.getAppTraceTag() != null) {
                 // Use the job's ID to distinguish traces since the ID will be unique per app.
-                Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, "JobScheduler",
-                        job.getAppTraceTag(), job.getJobId());
+                Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP,
+                        JobSchedulerService.TRACE_TRACK_NAME, job.getAppTraceTag(),
+                        job.getJobId());
             }
             try {
                 mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid());
@@ -1605,12 +1593,12 @@
                 completedJob.getFilteredTraceTag(),
                 completedJob.getFilteredDebugTags());
         if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
-            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
-                    getId());
+            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER,
+                    JobSchedulerService.TRACE_TRACK_NAME, getId());
         }
         if (completedJob.getAppTraceTag() != null) {
-            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, "JobScheduler",
-                    completedJob.getJobId());
+            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP,
+                    JobSchedulerService.TRACE_TRACK_NAME, completedJob.getJobId());
         }
         try {
             mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(),
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 852b00b..d5a58d1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -1771,7 +1771,13 @@
                     final int logicalIndex = mapping.getLogicalSlotIndex();
                     if (mCarrierPrivilegedCallbacks.contains(logicalIndex)) {
                         // Callback already exists. No need to create a new one or remove it.
-                        callbacksToRemove.remove(logicalIndex);
+                        for (int i = callbacksToRemove.size() - 1; i >= 0; i--) {
+                            if (callbacksToRemove.get(i) == logicalIndex) {
+                                callbacksToRemove.remove(i);
+                                break;
+                            }
+                        }
+
                         continue;
                     }
                     final LogicalIndexCarrierPrivilegesCallback callback =
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 7fca867..e3af1d8 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
@@ -572,6 +572,9 @@
     /** The reason a job most recently went from ready to not ready. */
     private int mReasonReadyToUnready = JobParameters.STOP_REASON_UNDEFINED;
 
+    /** The system trace tag for this job. */
+    private String mSystemTraceTag;
+
     /**
      * Core constructor for JobStatus instances.  All other ctors funnel down to this one.
      *
@@ -1058,6 +1061,38 @@
         return job.getTraceTag();
     }
 
+    /** Returns a trace tag using debug information provided by job scheduler service. */
+    @NonNull
+    public String computeSystemTraceTag() {
+        // Guarded by JobSchedulerService.mLock, no need for synchronization.
+        if (mSystemTraceTag != null) {
+            return mSystemTraceTag;
+        }
+
+        mSystemTraceTag = computeSystemTraceTagInner();
+        return mSystemTraceTag;
+    }
+
+    @NonNull
+    private String computeSystemTraceTagInner() {
+        final String componentPackage = getServiceComponent().getPackageName();
+        StringBuilder traceTag = new StringBuilder(128);
+        traceTag.append("*job*<").append(sourceUid).append(">").append(sourcePackageName);
+        if (!sourcePackageName.equals(componentPackage)) {
+            traceTag.append(":").append(componentPackage);
+        }
+        traceTag.append("/").append(getServiceComponent().getShortClassName());
+        if (!componentPackage.equals(serviceProcessName)) {
+            traceTag.append("$").append(serviceProcessName);
+        }
+        if (mNamespace != null && !mNamespace.trim().isEmpty()) {
+            traceTag.append("@").append(mNamespace);
+        }
+        traceTag.append("#").append(getJobId());
+
+        return traceTag.toString();
+    }
+
     /** Returns whether this job was scheduled by one app on behalf of another. */
     public boolean isProxyJob() {
         return mIsProxyJob;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index c240b3f..a1c72fb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -50,6 +50,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.util.ArraySet;
@@ -2181,6 +2182,12 @@
                     }
                     scheduleCutoff();
                 }
+            } else {
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+                    Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER,
+                            JobSchedulerService.TRACE_TRACK_NAME,
+                            "QC/- " + mPkg);
+                }
             }
         }
 
@@ -2720,6 +2727,11 @@
                         if (timeRemainingMs <= 50) {
                             // Less than 50 milliseconds left. Start process of shutting down jobs.
                             if (DEBUG) Slog.d(TAG, pkg + " has reached its quota.");
+                            if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+                                Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER,
+                                        JobSchedulerService.TRACE_TRACK_NAME,
+                                        pkg + "#" + MSG_REACHED_TIME_QUOTA);
+                            }
                             mStateChangedListener.onControllerStateChanged(
                                     maybeUpdateConstraintForPkgLocked(
                                             sElapsedRealtimeClock.millis(),
@@ -2748,6 +2760,11 @@
                                 pkg.userId, pkg.packageName);
                         if (timeRemainingMs <= 0) {
                             if (DEBUG) Slog.d(TAG, pkg + " has reached its EJ quota.");
+                            if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+                                Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER,
+                                        JobSchedulerService.TRACE_TRACK_NAME,
+                                        pkg + "#" + MSG_REACHED_EJ_TIME_QUOTA);
+                            }
                             mStateChangedListener.onControllerStateChanged(
                                     maybeUpdateConstraintForPkgLocked(
                                             sElapsedRealtimeClock.millis(),
@@ -2772,6 +2789,12 @@
                             Slog.d(TAG, pkg + " has reached its count quota.");
                         }
 
+                        if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+                            Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER,
+                                    JobSchedulerService.TRACE_TRACK_NAME,
+                                    pkg + "#" + MSG_REACHED_COUNT_QUOTA);
+                        }
+
                         mStateChangedListener.onControllerStateChanged(
                                 maybeUpdateConstraintForPkgLocked(
                                         sElapsedRealtimeClock.millis(),
@@ -2928,6 +2951,11 @@
                             }
                             mTempAllowlistGraceCache.delete(uid);
                             mTopAppGraceCache.delete(uid);
+                            if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+                                Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER,
+                                        JobSchedulerService.TRACE_TRACK_NAME,
+                                        "<" + uid + ">#" + MSG_END_GRACE_PERIOD);
+                            }
                             final ArraySet<String> packages = mService.getPackagesForUidLocked(uid);
                             if (packages != null) {
                                 final int userId = UserHandle.getUserId(uid);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
index 7aab67a..555a118 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
@@ -62,10 +62,11 @@
      * fine with it).
      *
      * @param job to be checked
+     * @param bias job bias to be checked
      * @return false if the {@link JobSchedulerService} should not schedule this job at the moment,
      * true - otherwise
      */
-    public abstract boolean isJobRestricted(JobStatus job);
+    public abstract boolean isJobRestricted(JobStatus job, int bias);
 
     /** Dump any internal constants the Restriction may have. */
     public abstract void dumpConstants(IndentingPrintWriter pw);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
index ef634b5..ba01113 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
@@ -24,6 +24,7 @@
 import android.util.IndentingPrintWriter;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.job.Flags;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.controllers.JobStatus;
 
@@ -85,7 +86,18 @@
     }
 
     @Override
-    public boolean isJobRestricted(JobStatus job) {
+    public boolean isJobRestricted(JobStatus job, int bias) {
+        if (Flags.thermalRestrictionsToFgsJobs()) {
+            if (bias >= JobInfo.BIAS_TOP_APP) {
+                // Jobs with BIAS_TOP_APP should not be restricted
+                return false;
+            }
+        } else {
+            if (bias >= JobInfo.BIAS_FOREGROUND_SERVICE) {
+                // Jobs with BIAS_FOREGROUND_SERVICE or higher should not be restricted
+                return false;
+            }
+        }
         if (mThermalStatus >= UPPER_THRESHOLD) {
             return true;
         }
@@ -107,6 +119,17 @@
                         || (mService.isCurrentlyRunningLocked(job)
                                 && mService.isJobInOvertimeLocked(job));
             }
+            if (Flags.thermalRestrictionsToFgsJobs()) {
+                // Only let foreground jobs run if:
+                // 1. They haven't previously run
+                // 2. They're already running and aren't yet in overtime
+                if (bias >= JobInfo.BIAS_FOREGROUND_SERVICE
+                        && job.getJob().isImportantWhileForeground()) {
+                    return job.getNumPreviousAttempts() > 0
+                            || (mService.isCurrentlyRunningLocked(job)
+                                    && mService.isJobInOvertimeLocked(job));
+                }
+            }
             if (priority == JobInfo.PRIORITY_HIGH) {
                 return !mService.isCurrentlyRunningLocked(job)
                         || mService.isJobInOvertimeLocked(job);
@@ -114,6 +137,13 @@
             return true;
         }
         if (mThermalStatus >= LOW_PRIORITY_THRESHOLD) {
+            if (Flags.thermalRestrictionsToFgsJobs()) {
+                if (bias >= JobInfo.BIAS_FOREGROUND_SERVICE) {
+                    // No restrictions on foreground jobs
+                    // on LOW_PRIORITY_THRESHOLD and below
+                    return false;
+                }
+            }
             // For light throttling, throttle all min priority jobs and all low priority jobs that
             // aren't already running or have been running for long enough.
             return priority == JobInfo.PRIORITY_MIN
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 9b81bd4..13d6ae5 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -50,7 +50,7 @@
         },
         api_lint: {
             enabled: true,
-            new_since: ":android.api.public.latest",
+            new_since: ":android.api.combined.public.latest",
             baseline_file: ":non-updatable-lint-baseline.txt",
         },
     },
@@ -130,7 +130,7 @@
         },
         api_lint: {
             enabled: true,
-            new_since: ":android.api.system.latest",
+            new_since: ":android.api.combined.system.latest",
             baseline_file: ":non-updatable-system-lint-baseline.txt",
         },
     },
@@ -185,7 +185,7 @@
         },
         api_lint: {
             enabled: true,
-            new_since: ":android.api.test.latest",
+            new_since: ":android.api.combined.test.latest",
             baseline_file: ":non-updatable-test-lint-baseline.txt",
         },
     },
@@ -269,7 +269,7 @@
         },
         api_lint: {
             enabled: true,
-            new_since: ":android.api.module-lib.latest",
+            new_since: ":android.api.combined.module-lib.latest",
             baseline_file: ":non-updatable-module-lib-lint-baseline.txt",
         },
     },
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 77b74e9..5adcd93 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -707,11 +707,11 @@
     eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
     eglDestroySurface(mDisplay, mSurface);
 
-    mFlingerSurfaceControl->updateDefaultBufferSize(newWidth, newHeight);
     const auto limitedSize = limitSurfaceSize(newWidth, newHeight);
     mWidth = limitedSize.width;
     mHeight = limitedSize.height;
 
+    mFlingerSurfaceControl->updateDefaultBufferSize(mWidth, mHeight);
     EGLConfig config = getEglConfig(mDisplay);
     EGLSurface surface = eglCreateWindowSurface(mDisplay, config, mFlingerSurface.get(), nullptr);
     if (eglMakeCurrent(mDisplay, surface, surface, mContext) == EGL_FALSE) {
diff --git a/cmds/bootanimation/FORMAT.md b/cmds/bootanimation/FORMAT.md
index 01e8fe1..da8331a 100644
--- a/cmds/bootanimation/FORMAT.md
+++ b/cmds/bootanimation/FORMAT.md
@@ -126,7 +126,7 @@
 Use `zopflipng` if you have it, otherwise `pngcrush` will do. e.g.:
 
     for fn in *.png ; do
-        zopflipng -m ${fn}s ${fn}s.new && mv -f ${fn}s.new ${fn}
+        zopflipng -m ${fn} ${fn}.new && mv -f ${fn}.new ${fn}
         # or: pngcrush -q ....
     done
 
diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
index 488292d..f726361 100644
--- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
+++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
@@ -292,13 +292,17 @@
         int childCount = node.getChildCount();
         for (int x = 0; x < childCount; x++) {
             AccessibilityNodeInfo childNode = node.getChild(x);
-
+            if (childNode == null) {
+                continue;
+            }
             if (!safeCharSeqToString(childNode.getContentDescription()).isEmpty()
-                    || !safeCharSeqToString(childNode.getText()).isEmpty())
+                    || !safeCharSeqToString(childNode.getText()).isEmpty()) {
                 return true;
+            }
 
-            if (childNafCheck(childNode))
+            if (childNafCheck(childNode)) {
                 return true;
+            }
         }
         return false;
     }
diff --git a/config/Android.bp b/config/Android.bp
index adce203..c9948c3 100644
--- a/config/Android.bp
+++ b/config/Android.bp
@@ -33,7 +33,7 @@
     name: "preloaded-classes",
     src: "preloaded-classes",
     filename: "preloaded-classes",
-    installable: false,
+    no_full_install: true,
 }
 
 filegroup {
diff --git a/core/api/current.txt b/core/api/current.txt
index 53cf7d5..13958d2 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -26592,7 +26592,7 @@
     method public long getFlags();
     method @Nullable public android.media.MediaMetadata getMetadata();
     method public String getPackageName();
-    method @Nullable public android.media.session.MediaController.PlaybackInfo getPlaybackInfo();
+    method @NonNull public android.media.session.MediaController.PlaybackInfo getPlaybackInfo();
     method @Nullable public android.media.session.PlaybackState getPlaybackState();
     method @Nullable public java.util.List<android.media.session.MediaSession.QueueItem> getQueue();
     method @Nullable public CharSequence getQueueTitle();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index eabe1f1..96315eb 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1143,16 +1143,13 @@
     field public static final int NAV_BAR_MODE_KIDS = 1; // 0x1
   }
 
-  public static final class StatusBarManager.DisableInfo implements android.os.Parcelable {
+  public static final class StatusBarManager.DisableInfo {
     method public boolean areAllComponentsEnabled();
-    method public int describeContents();
     method public boolean isNavigateToHomeDisabled();
     method public boolean isNotificationPeekingDisabled();
     method public boolean isRecentsDisabled();
     method public boolean isSearchDisabled();
     method public boolean isStatusBarExpansionDisabled();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.app.StatusBarManager.DisableInfo> CREATOR;
   }
 
   public final class SystemServiceRegistry {
@@ -10342,7 +10339,7 @@
     method public int getDeviceType();
     method @NonNull public android.os.Bundle getExtras();
     method @NonNull public String getModelName();
-    method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") public boolean isBatteryCharging();
+    method @FlaggedApi("android.net.wifi.flags.network_provider_battery_charging_status") public boolean isBatteryCharging();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.NetworkProviderInfo> CREATOR;
     field public static final int DEVICE_TYPE_AUTO = 5; // 0x5
@@ -10356,7 +10353,7 @@
   public static final class NetworkProviderInfo.Builder {
     ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build();
-    method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean);
+    method @FlaggedApi("android.net.wifi.flags.network_provider_battery_charging_status") @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String);
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 62fc67b..78577e2 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -1939,10 +1939,6 @@
     New API must be flagged with @FlaggedApi: method android.app.ActivityManager.getExternalHistoricalProcessStartReasons(String,int)
 UnflaggedApi: android.app.AppOpsManager#OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO:
     New API must be flagged with @FlaggedApi: field android.app.AppOpsManager.OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO
-UnflaggedApi: android.app.StatusBarManager.DisableInfo#CREATOR:
-    New API must be flagged with @FlaggedApi: field android.app.StatusBarManager.DisableInfo.CREATOR
-UnflaggedApi: android.app.StatusBarManager.DisableInfo#isBackDisabled():
-    New API must be flagged with @FlaggedApi: method android.app.StatusBarManager.DisableInfo.isBackDisabled()
 UnflaggedApi: android.companion.virtual.VirtualDeviceManager.VirtualDevice#getPersistentDeviceId():
     New API must be flagged with @FlaggedApi: method android.companion.virtual.VirtualDeviceManager.VirtualDevice.getPersistentDeviceId()
 UnflaggedApi: android.content.Context#THREAD_NETWORK_SERVICE:
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 2437be8..14ae3f5 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -463,7 +463,7 @@
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void togglePanel();
   }
 
-  public static final class StatusBarManager.DisableInfo implements android.os.Parcelable {
+  public static final class StatusBarManager.DisableInfo {
     method public boolean isRotationSuggestionDisabled();
   }
 
@@ -966,7 +966,6 @@
     ctor public AttributionSource(int, @Nullable String, @Nullable String);
     ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder);
     ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource);
-    ctor @FlaggedApi("android.permission.flags.attribution_source_constructor") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], @Nullable android.content.AttributionSource);
     ctor @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource);
     method public void enforceCallingPid();
   }
@@ -1775,16 +1774,16 @@
   }
 
   public final class InputManager {
-    method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void addUniqueIdAssociation(@NonNull String, @NonNull String);
     method @FlaggedApi("com.android.input.flags.device_associations") @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void addUniqueIdAssociationByDescriptor(@NonNull String, @NonNull String);
+    method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void addUniqueIdAssociationByPort(@NonNull String, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void clearAllModifierKeyRemappings();
     method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptors();
     method @NonNull public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public java.util.Map<java.lang.Integer,java.lang.Integer> getModifierKeyRemapping();
     method public int getMousePointerSpeed();
     method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void remapModifierKey(int, int);
-    method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociation(@NonNull String);
     method @FlaggedApi("com.android.input.flags.device_associations") @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociationByDescriptor(@NonNull String);
+    method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociationByPort(@NonNull String);
     field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL
   }
 
@@ -4261,6 +4260,12 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentOrganizerToken> CREATOR;
   }
 
+  public final class TaskFragmentParentInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentParentInfo> CREATOR;
+  }
+
   public final class TaskFragmentTransaction implements android.os.Parcelable {
     ctor public TaskFragmentTransaction();
     method public void addChange(@Nullable android.window.TaskFragmentTransaction.Change);
@@ -4285,8 +4290,8 @@
     method @Nullable public android.os.IBinder getActivityToken();
     method @NonNull public android.os.Bundle getErrorBundle();
     method @Nullable public android.os.IBinder getErrorCallbackToken();
-    method @Nullable public android.content.res.Configuration getTaskConfiguration();
     method @Nullable public android.window.TaskFragmentInfo getTaskFragmentInfo();
+    method @Nullable public android.window.TaskFragmentParentInfo getTaskFragmentParentInfo();
     method @Nullable public android.os.IBinder getTaskFragmentToken();
     method public int getTaskId();
     method public int getType();
@@ -4294,7 +4299,6 @@
     method @NonNull public android.window.TaskFragmentTransaction.Change setActivityToken(@NonNull android.os.IBinder);
     method @NonNull public android.window.TaskFragmentTransaction.Change setErrorBundle(@NonNull android.os.Bundle);
     method @NonNull public android.window.TaskFragmentTransaction.Change setErrorCallbackToken(@Nullable android.os.IBinder);
-    method @NonNull public android.window.TaskFragmentTransaction.Change setTaskConfiguration(@NonNull android.content.res.Configuration);
     method @NonNull public android.window.TaskFragmentTransaction.Change setTaskFragmentInfo(@NonNull android.window.TaskFragmentInfo);
     method @NonNull public android.window.TaskFragmentTransaction.Change setTaskFragmentToken(@NonNull android.os.IBinder);
     method @NonNull public android.window.TaskFragmentTransaction.Change setTaskId(int);
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index c4fe061..6cc71e5 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -975,14 +975,14 @@
     Method 'getHdmiCecVersion' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.hardware.hdmi.HdmiControlManager#setHdmiCecVersion(int):
     Method 'setHdmiCecVersion' documentation mentions permissions already declared by @RequiresPermission
-RequiresPermission: android.hardware.input.InputManager#addUniqueIdAssociation(String, String):
-    Method 'addUniqueIdAssociation' documentation mentions permissions without declaring @RequiresPermission
 RequiresPermission: android.hardware.input.InputManager#addUniqueIdAssociationByDescriptor(String, String):
     Method 'addUniqueIdAssociationByDescriptor' documentation mentions permissions already declared by @RequiresPermission
-RequiresPermission: android.hardware.input.InputManager#removeUniqueIdAssociation(String):
-    Method 'removeUniqueIdAssociation' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.hardware.input.InputManager#addUniqueIdAssociationByPort(String, String):
+    Method 'addUniqueIdAssociationByPort' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.hardware.input.InputManager#removeUniqueIdAssociationByDescriptor(String):
     Method 'removeUniqueIdAssociationByDescriptor' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.hardware.input.InputManager#removeUniqueIdAssociationByPort(String):
+    Method 'removeUniqueIdAssociationByPort' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.hardware.location.GeofenceHardware#addGeofence(int, int, android.hardware.location.GeofenceHardwareRequest, android.hardware.location.GeofenceHardwareCallback):
     Method 'addGeofence' documentation mentions permissions without declaring @RequiresPermission
 RequiresPermission: android.hardware.location.GeofenceHardware#getMonitoringTypes():
@@ -2035,6 +2035,10 @@
     New API must be flagged with @FlaggedApi: method android.content.pm.UserInfo.isPrivateProfile()
 UnflaggedApi: android.credentials.CredentialProviderInfo#isPrimary():
     New API must be flagged with @FlaggedApi: method android.credentials.CredentialProviderInfo.isPrimary()
+UnflaggedApi: android.hardware.input.InputManager#addUniqueIdAssociationByPort(String, String):
+    New API must be flagged with @FlaggedApi: method android.hardware.input.InputManager.addUniqueIdAssociationByPort(String,String)
+UnflaggedApi: android.hardware.input.InputManager#removeUniqueIdAssociationByPort(String):
+    New API must be flagged with @FlaggedApi: method android.hardware.input.InputManager.removeUniqueIdAssociationByPort(String)
 UnflaggedApi: android.media.AudioManager#enterAudioFocusFreezeForTest(java.util.List<java.lang.Integer>):
     New API must be flagged with @FlaggedApi: method android.media.AudioManager.enterAudioFocusFreezeForTest(java.util.List<java.lang.Integer>)
 UnflaggedApi: android.media.AudioManager#exitAudioFocusFreezeForTest():
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index dc5c7f6..713d8e5 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -43,131 +43,189 @@
  */
 interface IAccessibilityServiceConnection {
 
+    @RequiresNoPermission
     void setServiceInfo(in AccessibilityServiceInfo info);
 
+    @RequiresNoPermission
     void setAttributionTag(in String attributionTag);
 
+    @RequiresNoPermission
     String[] findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
         long accessibilityNodeId, int interactionId,
         IAccessibilityInteractionConnectionCallback callback, int flags, long threadId,
         in Bundle arguments);
 
+    @RequiresNoPermission
     String[] findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId,
         String text, int interactionId, IAccessibilityInteractionConnectionCallback callback,
         long threadId);
 
+    @RequiresNoPermission
     String[] findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
         long accessibilityNodeId, String viewId, int interactionId,
         IAccessibilityInteractionConnectionCallback callback, long threadId);
 
+    @RequiresNoPermission
     String[] findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType,
         int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId);
 
+    @RequiresNoPermission
     String[] focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction,
         int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId);
 
+    @RequiresNoPermission
     boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId,
         int action, in Bundle arguments, int interactionId,
         IAccessibilityInteractionConnectionCallback callback, long threadId);
 
+    @RequiresNoPermission
     AccessibilityWindowInfo getWindow(int windowId);
 
+    @RequiresNoPermission
     AccessibilityWindowInfo.WindowListSparseArray getWindows();
 
+    @RequiresNoPermission
     AccessibilityServiceInfo getServiceInfo();
 
+    @RequiresNoPermission
     boolean performGlobalAction(int action);
+
+    @RequiresNoPermission
     List<AccessibilityNodeInfo.AccessibilityAction> getSystemActions();
 
+    @RequiresNoPermission
     void disableSelf();
 
+    @RequiresNoPermission
     oneway void setOnKeyEventResult(boolean handled, int sequence);
 
+    @RequiresNoPermission
     MagnificationConfig getMagnificationConfig(int displayId);
 
+    @RequiresNoPermission
     float getMagnificationScale(int displayId);
 
+    @RequiresNoPermission
     float getMagnificationCenterX(int displayId);
 
+    @RequiresNoPermission
     float getMagnificationCenterY(int displayId);
 
+    @RequiresNoPermission
     Region getMagnificationRegion(int displayId);
 
+    @RequiresNoPermission
     Region getCurrentMagnificationRegion(int displayId);
 
+    @RequiresNoPermission
     boolean resetMagnification(int displayId, boolean animate);
 
+    @RequiresNoPermission
     boolean resetCurrentMagnification(int displayId, boolean animate);
 
+    @RequiresNoPermission
     boolean setMagnificationConfig(int displayId, in MagnificationConfig config, boolean animate);
 
+    @RequiresNoPermission
     void setMagnificationCallbackEnabled(int displayId, boolean enabled);
 
+    @RequiresNoPermission
     boolean setSoftKeyboardShowMode(int showMode);
 
+    @RequiresNoPermission
     int getSoftKeyboardShowMode();
 
+    @RequiresNoPermission
     void setSoftKeyboardCallbackEnabled(boolean enabled);
 
-    boolean switchToInputMethod(String imeId);
+    @RequiresNoPermission
+        boolean switchToInputMethod(String imeId);
 
-    int setInputMethodEnabled(String imeId, boolean enabled);
+    @RequiresNoPermission
+        int setInputMethodEnabled(String imeId, boolean enabled);
 
+    @RequiresNoPermission
     boolean isAccessibilityButtonAvailable();
 
+    @RequiresNoPermission
     void sendGesture(int sequence, in ParceledListSlice gestureSteps);
 
+    @RequiresNoPermission
     void dispatchGesture(int sequence, in ParceledListSlice gestureSteps, int displayId);
 
+    @RequiresNoPermission
     boolean isFingerprintGestureDetectionAvailable();
 
+    @RequiresNoPermission
     IBinder getOverlayWindowToken(int displayid);
 
+    @RequiresNoPermission
     int getWindowIdForLeashToken(IBinder token);
 
+    @RequiresNoPermission
     void takeScreenshot(int displayId, in RemoteCallback callback);
 
+    @RequiresNoPermission
     void takeScreenshotOfWindow(int accessibilityWindowId, int interactionId,
         in ScreenCapture.ScreenCaptureListener listener,
         IAccessibilityInteractionConnectionCallback callback);
 
+    @RequiresNoPermission
     void setGestureDetectionPassthroughRegion(int displayId, in Region region);
 
+    @RequiresNoPermission
     void setTouchExplorationPassthroughRegion(int displayId, in Region region);
 
+    @RequiresNoPermission
     void setFocusAppearance(int strokeWidth, int color);
 
+    @RequiresNoPermission
     void setCacheEnabled(boolean enabled);
 
+    @RequiresNoPermission
     oneway void logTrace(long timestamp, String where, long loggingTypes, String callingParams,
         int processId, long threadId, int callingUid, in Bundle serializedCallingStackInBundle);
 
+    @RequiresNoPermission
     void setServiceDetectsGesturesEnabled(int displayId, boolean mode);
 
+    @RequiresNoPermission
     void requestTouchExploration(int displayId);
 
+    @RequiresNoPermission
     void requestDragging(int displayId, int pointerId);
 
+    @RequiresNoPermission
     void requestDelegating(int displayId);
 
+    @RequiresNoPermission
     void onDoubleTap(int displayId);
 
+    @RequiresNoPermission
     void onDoubleTapAndHold(int displayId);
 
+    @RequiresNoPermission
     void setAnimationScale(float scale);
 
+    @RequiresNoPermission
     void setInstalledAndEnabledServices(in List<AccessibilityServiceInfo> infos);
 
-    List<AccessibilityServiceInfo> getInstalledAndEnabledServices();
+    @RequiresNoPermission
+        List<AccessibilityServiceInfo> getInstalledAndEnabledServices();
+
+    @RequiresNoPermission
     void attachAccessibilityOverlayToDisplay(int interactionId, int displayId, in SurfaceControl sc, IAccessibilityInteractionConnectionCallback callback);
 
+    @RequiresNoPermission
     void attachAccessibilityOverlayToWindow(int interactionId, int accessibilityWindowId, in SurfaceControl sc, IAccessibilityInteractionConnectionCallback callback);
 
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
+    @EnforcePermission("BLUETOOTH_CONNECT")
     void connectBluetoothBrailleDisplay(in String bluetoothAddress, in IBrailleDisplayController controller);
 
+
+    @RequiresNoPermission
     void connectUsbBrailleDisplay(in UsbDevice usbDevice, in IBrailleDisplayController controller);
 
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
+    @EnforcePermission("MANAGE_ACCESSIBILITY")
     void setTestBrailleDisplayData(in List<Bundle> brailleDisplays);
 }
\ No newline at end of file
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 7ee3413..497d47a 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -2015,7 +2015,7 @@
      *     null for no callback
      * @param handler {@link Handler} identifying the callback thread,
      *     null for the main thread
-     * @return An {@link AccountManagerFuture} which resolves to a Boolean indicated wether it
+     * @return An {@link AccountManagerFuture} which resolves to a Boolean indicated whether it
      * succeeded.
      * @hide
      */
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a5dd4a7..67c0190 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1183,6 +1183,7 @@
      * @see #setIntent(Intent, ComponentCaller)
      */
     @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
+    @SuppressLint("OnNameExpected")
     public @Nullable ComponentCaller getCaller() {
         return mCaller;
     }
@@ -1203,6 +1204,7 @@
      * @see #getCaller
      */
     @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
+    @SuppressLint("OnNameExpected")
     public void setIntent(@Nullable Intent newIntent, @Nullable ComponentCaller newCaller) {
         internalSetIntent(newIntent, newCaller);
     }
@@ -5796,6 +5798,8 @@
      * @see #onRequestPermissionsResult
      */
     @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+    @SuppressLint("OnNameExpected")
+    // Suppress lint as this is an overload of the original API.
     public boolean shouldShowRequestPermissionRationale(@NonNull String permission, int deviceId) {
         final PackageManager packageManager = getDeviceId() == deviceId ? getPackageManager()
                 : createDeviceContext(deviceId).getPackageManager();
@@ -7159,6 +7163,7 @@
      * @see ComponentCaller
      */
     @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
+    @SuppressLint("OnNameExpected")
     public @NonNull ComponentCaller getInitialCaller() {
         return mInitialCaller;
     }
@@ -7186,10 +7191,11 @@
      * @see #getCaller
      */
     @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
+    @SuppressLint("OnNameExpected")
     public @NonNull ComponentCaller getCurrentCaller() {
         if (mCurrentCaller == null) {
             throw new IllegalStateException("The caller is null because #getCurrentCaller should be"
-                    + " called within #onNewIntent method");
+                    + " called within #onNewIntent or #onActivityResult methods");
         }
         return mCurrentCaller;
     }
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index d8df447..8e99e46b 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -1282,4 +1282,15 @@
      */
     public abstract void addStartInfoTimestamp(int key, long timestampNs, int uid, int pid,
             int userId);
+
+    /**
+     * It is similar {@link IActivityManager#killApplication(String, int, int, String, int)} but
+     * it immediately stop the package.
+     *
+     * <p>Note: Do not call this method from inside PMS's lock, otherwise it'll run into
+     * watchdog reset.
+     * @hide
+     */
+    public abstract void killApplicationSync(String pkgName, int appId, int userId,
+            String reason, int exitInfoReason);
 }
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index be8f48d..c8ab260 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -90,13 +90,6 @@
     public static final int RESIZE_MODE_USER = RESIZE_MODE_PRESERVE_WINDOW;
 
     /**
-     * Input parameter to {@link IActivityTaskManager#resizeTask} used by window
-     * manager during a screen rotation.
-     * @hide
-     */
-    public static final int RESIZE_MODE_SYSTEM_SCREEN_ROTATION = RESIZE_MODE_PRESERVE_WINDOW;
-
-    /**
      * Input parameter to {@link IActivityTaskManager#resizeTask} which indicates
      * that the resize should be performed even if the bounds appears unchanged.
      * @hide
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index caaaf51..d4812dd 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -50,6 +50,7 @@
 import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
 import android.app.RemoteServiceException.CrashedByAdbException;
 import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException;
+import android.app.RemoteServiceException.ForegroundServiceDidNotStopInTimeException;
 import android.app.RemoteServiceException.MissingRequestPasswordComplexityPermissionException;
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
@@ -2236,6 +2237,9 @@
             case ForegroundServiceDidNotStartInTimeException.TYPE_ID:
                 throw generateForegroundServiceDidNotStartInTimeException(message, extras);
 
+            case ForegroundServiceDidNotStopInTimeException.TYPE_ID:
+                throw generateForegroundServiceDidNotStopInTimeException(message, extras);
+
             case CannotPostForegroundServiceNotificationException.TYPE_ID:
                 throw new CannotPostForegroundServiceNotificationException(message);
 
@@ -2266,6 +2270,15 @@
         throw new ForegroundServiceDidNotStartInTimeException(message, inner);
     }
 
+    private ForegroundServiceDidNotStopInTimeException
+            generateForegroundServiceDidNotStopInTimeException(String message, Bundle extras) {
+        final String serviceClassName =
+                ForegroundServiceDidNotStopInTimeException.getServiceClassNameFromExtras(extras);
+        final Exception inner = (serviceClassName == null) ? null
+                : Service.getStartForegroundServiceStackTrace(serviceClassName);
+        throw new ForegroundServiceDidNotStopInTimeException(message, inner);
+    }
+
     class H extends Handler {
         public static final int BIND_APPLICATION        = 110;
         @UnsupportedAppUsage
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java
index 7724c23..92543b1 100644
--- a/core/java/android/app/AppCompatTaskInfo.java
+++ b/core/java/android/app/AppCompatTaskInfo.java
@@ -32,6 +32,11 @@
     public boolean topActivityEligibleForLetterboxEducation;
 
     /**
+     * Whether the letterbox education is enabled
+     */
+    public boolean isLetterboxEducationEnabled;
+
+    /**
      * Whether the direct top activity is in size compat mode on foreground.
      */
     public boolean topActivityInSizeCompat;
@@ -178,6 +183,7 @@
                     == that.topActivityEligibleForUserAspectRatioButton
                 && topActivityEligibleForLetterboxEducation
                     == that.topActivityEligibleForLetterboxEducation
+                && isLetterboxEducationEnabled == that.isLetterboxEducationEnabled
                 && topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition
                 && topActivityLetterboxHorizontalPosition
                     == that.topActivityLetterboxHorizontalPosition
@@ -192,6 +198,7 @@
      * Reads the AppCompatTaskInfo from a parcel.
      */
     void readFromParcel(Parcel source) {
+        isLetterboxEducationEnabled = source.readBoolean();
         topActivityInSizeCompat = source.readBoolean();
         topActivityEligibleForLetterboxEducation = source.readBoolean();
         isLetterboxDoubleTapEnabled = source.readBoolean();
@@ -212,6 +219,7 @@
      */
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        dest.writeBoolean(isLetterboxEducationEnabled);
         dest.writeBoolean(topActivityInSizeCompat);
         dest.writeBoolean(topActivityEligibleForLetterboxEducation);
         dest.writeBoolean(isLetterboxDoubleTapEnabled);
@@ -232,6 +240,7 @@
         return "AppCompatTaskInfo { topActivityInSizeCompat=" + topActivityInSizeCompat
                 + " topActivityEligibleForLetterboxEducation= "
                 + topActivityEligibleForLetterboxEducation
+                + "isLetterboxEducationEnabled= " + isLetterboxEducationEnabled
                 + " isLetterboxDoubleTapEnabled= " + isLetterboxDoubleTapEnabled
                 + " topActivityEligibleForUserAspectRatioButton= "
                 + topActivityEligibleForUserAspectRatioButton
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 2d0f6fc..6865f9c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -482,7 +482,8 @@
             UID_STATE_FOREGROUND_SERVICE,
             UID_STATE_FOREGROUND,
             UID_STATE_BACKGROUND,
-            UID_STATE_CACHED
+            UID_STATE_CACHED,
+            UID_STATE_NONEXISTENT
     })
     public @interface UidState {}
 
@@ -566,6 +567,12 @@
     public static final int MIN_PRIORITY_UID_STATE = UID_STATE_CACHED;
 
     /**
+     * Special uid state: The UID is not running
+     * @hide
+     */
+    public static final int UID_STATE_NONEXISTENT = Integer.MAX_VALUE;
+
+    /**
      * Resolves the first unrestricted state given an app op.
      * @param op The op to resolve.
      * @return The last restricted UID state.
@@ -596,6 +603,7 @@
             UID_STATE_FOREGROUND,
             UID_STATE_BACKGROUND,
             UID_STATE_CACHED
+            // UID_STATE_NONEXISTENT isn't a real UID state, so it is excluded
     };
 
     /** @hide */
@@ -615,6 +623,8 @@
                 return "bg";
             case UID_STATE_CACHED:
                 return "cch";
+            case UID_STATE_NONEXISTENT:
+                return "gone";
             default:
                 return "unknown";
         }
@@ -3573,6 +3583,9 @@
             return mAttributionTag;
         }
 
+        /**
+         * Persistent device Id of the proxy that noted the op
+         */
         @FlaggedApi(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED)
         public @Nullable String getDeviceId() { return mDeviceId; }
 
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 0e20138..cd7f1e4 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -679,11 +679,13 @@
         if (keyCode == KeyEvent.KEYCODE_ESCAPE) {
             if (mCancelable) {
                 cancel();
-            } else {
+                event.startTracking();
+                return true;
+            } else if (mWindow.shouldCloseOnTouchOutside()) {
                 dismiss();
+                event.startTracking();
+                return true;
             }
-            event.startTracking();
-            return true;
         }
 
         return false;
diff --git a/core/java/android/app/DreamManager.java b/core/java/android/app/DreamManager.java
index ef6982e..4ac40a1 100644
--- a/core/java/android/app/DreamManager.java
+++ b/core/java/android/app/DreamManager.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
 
+import android.annotation.FlaggedApi;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
@@ -30,6 +31,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.dreams.DreamService;
+import android.service.dreams.Flags;
 import android.service.dreams.IDreamManager;
 
 /**
@@ -217,4 +219,19 @@
         }
         return false;
     }
+
+    /**
+     * Sets whether the dream is obscured by something.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_DREAM_HANDLES_BEING_OBSCURED)
+    @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)
+    public void setDreamIsObscured(boolean isObscured) {
+        try {
+            mService.setDreamIsObscured(isObscured);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index c3bac71..7ea3091 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -114,6 +114,7 @@
 import com.android.internal.graphics.ColorUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ContrastColorUtil;
+import com.android.internal.util.NewlineNormalizer;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -3190,7 +3191,7 @@
             return charSequence;
         }
 
-        return charSequence.toString().replaceAll("[\r\n]+", "\n");
+        return NewlineNormalizer.normalizeNewlines(charSequence.toString());
     }
 
     private static CharSequence removeTextSizeSpans(CharSequence charSequence) {
@@ -5703,6 +5704,7 @@
             p.headerless(resId == getBaseLayoutResource()
                     || resId == getHeadsUpBaseLayoutResource()
                     || resId == getCompactHeadsUpBaseLayoutResource()
+                    || resId == getMessagingCompactHeadsUpLayoutResource()
                     || resId == getMessagingLayoutResource()
                     || resId == R.layout.notification_template_material_media);
             RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
@@ -6490,6 +6492,12 @@
         // visual regressions.
         @SuppressWarnings("AndroidFrameworkCompatChange")
         private boolean bigContentViewRequired() {
+            if (Flags.notificationExpansionOptional()) {
+                // Notifications without a bigContentView, style, or actions do not need to expand
+                boolean exempt = mN.bigContentView == null
+                        && mStyle == null && mActions.size() == 0;
+                return !exempt;
+            }
             if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) {
                 return true;
             }
@@ -6593,12 +6601,12 @@
          * @hide
          */
         public RemoteViews createCompactHeadsUpContentView() {
-            // TODO(b/336225281): re-evaluate custom view usage.
-            if (useExistingRemoteView(mN.headsUpContentView)) {
-                return fullyCustomViewRequiresDecoration(false /* fromStyle */)
-                        ? minimallyDecoratedHeadsUpContentView(mN.headsUpContentView)
-                        : mN.headsUpContentView;
-            } else if (mStyle != null) {
+            // Don't show compact heads up for FSI notifications.
+            if (mN.fullScreenIntent != null) {
+                return createHeadsUpContentView(/* increasedHeight= */ false);
+            }
+
+            if (mStyle != null) {
                 final RemoteViews styleView = mStyle.makeCompactHeadsUpContentView();
                 if (styleView != null) {
                     return styleView;
@@ -6611,7 +6619,7 @@
             // Notification text is shown as secondary header text
             // for the minimal hun when it is provided.
             // Time(when and chronometer) is not shown for the minimal hun.
-            p.headerTextSecondary(p.mText).text(null).hideTime(true);
+            p.headerTextSecondary(p.mText).text(null).hideTime(true).summaryText("");
 
             return applyStandardTemplate(
                     getCompactHeadsUpBaseLayoutResource(), p,
@@ -7153,13 +7161,7 @@
             // Adds any new extras provided by the user.
             if (mUserExtras != null) {
                 final Bundle saveExtras = (Bundle) mUserExtras.clone();
-                if (SystemProperties.getBoolean(
-                        "persist.sysui.notification.builder_extras_override", false)) {
-                    mN.extras.putAll(saveExtras);
-                } else {
-                    saveExtras.putAll(mN.extras);
-                    mN.extras = saveExtras;
-                }
+                mN.extras.putAll(saveExtras);
             }
 
             if (!Flags.sortSectionByTime()) {
@@ -7298,6 +7300,10 @@
             return R.layout.notification_template_material_compact_heads_up_base;
         }
 
+        private int getMessagingCompactHeadsUpLayoutResource() {
+            return R.layout.notification_template_material_messaging_compact_heads_up;
+        }
+
         private int getBigBaseLayoutResource() {
             return R.layout.notification_template_material_big_base;
         }
@@ -8886,6 +8892,62 @@
             }
         }
 
+        private void fixTitleAndTextForCompactMessaging(StandardTemplateParams p) {
+            Message m = findLatestIncomingMessage();
+            final CharSequence text = (m == null) ? null : m.mText;
+            CharSequence sender = m == null ? null
+                    : m.mSender == null || TextUtils.isEmpty(m.mSender.getName())
+                            ? mUser.getName() : m.mSender.getName();
+
+            CharSequence conversationTitle = mIsGroupConversation ? mConversationTitle : null;
+
+            // we want to have colon for possible title for conversation.
+            final BidiFormatter bidi = BidiFormatter.getInstance();
+            if (sender != null) {
+                sender = mBuilder.mContext.getString(
+                        com.android.internal.R.string.notification_messaging_title_template,
+                        bidi.unicodeWrap(sender), "");
+            } else if (conversationTitle != null) {
+                conversationTitle = mBuilder.mContext.getString(
+                        com.android.internal.R.string.notification_messaging_title_template,
+                        bidi.unicodeWrap(conversationTitle), "");
+            }
+
+            if (Flags.cleanUpSpansAndNewLines()) {
+                conversationTitle = stripStyling(conversationTitle);
+                sender = stripStyling(sender);
+            }
+
+            final boolean showOnlySenderName = showOnlySenderName();
+
+            final CharSequence title;
+            boolean isConversationTitleAvailable = !showOnlySenderName && conversationTitle != null;
+            if (isConversationTitleAvailable) {
+                title = conversationTitle;
+            } else {
+                title = sender;
+            }
+
+            p.title(title);
+            // when the conversation title is available, use headerTextSecondary for sender and
+            // summaryText for text
+            if (isConversationTitleAvailable) {
+                p.headerTextSecondary(sender);
+                p.summaryText(text);
+            } else {
+                // when it is not, use headerTextSecondary for text and don't use summaryText
+                p.headerTextSecondary(text);
+                p.summaryText(null);
+            }
+        }
+
+        /** developer settings to always show sender name */
+        private boolean showOnlySenderName() {
+            return SystemProperties.getBoolean(
+                    "persist.compact_heads_up_notification.show_only_sender_name",
+                    false);
+        }
+
         /**
          * @hide
          */
@@ -9170,10 +9232,78 @@
         @Nullable
         @Override
         public RemoteViews makeCompactHeadsUpContentView() {
-            // TODO(b/336229954): Apply minimal HUN treatment to Messaging Notifications.
-            return makeHeadsUpContentView(false);
+            final boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY;
+            Icon conversationIcon = null;
+            Notification.Action remoteInputAction = null;
+            if (isConversationLayout) {
+
+                conversationIcon = mShortcutIcon;
+
+                // conversation icon is m
+                // Extract the conversation icon for one to one conversations from
+                // the latest incoming message since
+                // fixTitleAndTextExtras also uses it as data source for title and text
+                if (conversationIcon == null && !mIsGroupConversation) {
+                    final Message message = findLatestIncomingMessage();
+                    if (message != null) {
+                        final Person sender = message.mSender;
+                        if (sender != null) {
+                            conversationIcon = sender.getIcon();
+                        }
+                    }
+                }
+
+                if (Flags.compactHeadsUpNotificationReply()) {
+                    // Get the first non-contextual inline reply action.
+                    final List<Notification.Action> nonContextualActions =
+                            mBuilder.getNonContextualActions();
+                    for (int i = 0; i < nonContextualActions.size(); i++) {
+                        final Notification.Action action = nonContextualActions.get(i);
+                        if (mBuilder.hasValidRemoteInput(action)) {
+                            remoteInputAction = action;
+                            break;
+                        }
+                    }
+                }
+            }
+
+            final StandardTemplateParams p = mBuilder.mParams.reset()
+                    .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
+                    .highlightExpander(isConversationLayout)
+                    .fillTextsFrom(mBuilder)
+                    .hideTime(true);
+
+            fixTitleAndTextForCompactMessaging(p);
+            TemplateBindResult bindResult = new TemplateBindResult();
+
+            RemoteViews contentView = mBuilder.applyStandardTemplate(
+                    mBuilder.getMessagingCompactHeadsUpLayoutResource(), p, bindResult);
+            contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE);
+            contentView.setViewVisibility(R.id.header_text_divider, View.GONE);
+            if (conversationIcon != null) {
+                contentView.setViewVisibility(R.id.icon, View.GONE);
+                contentView.setViewVisibility(R.id.conversation_icon, View.VISIBLE);
+                contentView.setBoolean(R.id.conversation_icon, "setApplyCircularCrop", true);
+                contentView.setImageViewIcon(R.id.conversation_icon, conversationIcon);
+            }
+
+            if (remoteInputAction != null) {
+                contentView.setViewVisibility(R.id.reply_action_container, View.VISIBLE);
+
+                final RemoteViews inlineReplyButton =
+                        mBuilder.generateActionButton(remoteInputAction, false, p);
+                // Clear the drawable
+                inlineReplyButton.setInt(R.id.action0, "setBackgroundResource", 0);
+                inlineReplyButton.setTextViewText(R.id.action0,
+                        mBuilder.mContext.getString(R.string.notification_compact_heads_up_reply));
+                contentView.addView(R.id.reply_action_container, inlineReplyButton);
+            } else {
+                contentView.setViewVisibility(R.id.reply_action_container, View.GONE);
+            }
+            return contentView;
         }
 
+
         /**
          * @hide
          */
@@ -10355,7 +10485,7 @@
         @Nullable
         @Override
         public RemoteViews makeCompactHeadsUpContentView() {
-            // TODO(b/336228700): Apply minimal HUN treatment for Call Style.
+            // Use existing heads up for call style.
             return makeHeadsUpContentView(false);
         }
 
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index b82a1e3..e4310c1 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -2972,6 +2972,7 @@
         android.Manifest.permission.INTERACT_ACROSS_USERS,
         android.Manifest.permission.ACCESS_NOTIFICATIONS})
     @FlaggedApi(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
+    @SuppressLint("UserHandle")
     public void registerCallNotificationEventListener(@NonNull String packageName,
             @NonNull UserHandle userHandle, @NonNull @CallbackExecutor Executor executor,
             @NonNull CallNotificationEventListener listener) {
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 97c2e43..2e38c06 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -60,6 +60,9 @@
 # ComponentCaller
 per-file ComponentCaller.java = file:COMPONENT_CALLER_OWNERS
 
+# DreamManager
+per-file DreamManager.java = file:/DREAM_MANAGER_OWNERS
+
 # GrammaticalInflectionManager
 per-file *GrammaticalInflection* = file:/services/core/java/com/android/server/grammaticalinflection/OWNERS
 per-file grammatical_inflection_manager.aconfig = file:/services/core/java/com/android/server/grammaticalinflection/OWNERS
diff --git a/core/java/android/app/RemoteServiceException.java b/core/java/android/app/RemoteServiceException.java
index c5ad110..c624c43 100644
--- a/core/java/android/app/RemoteServiceException.java
+++ b/core/java/android/app/RemoteServiceException.java
@@ -71,6 +71,33 @@
     }
 
     /**
+     * Exception used to crash an app process when it didn't stop after hitting its time limit.
+     *
+     * @hide
+     */
+    public static class ForegroundServiceDidNotStopInTimeException extends RemoteServiceException {
+        /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
+        public static final int TYPE_ID = 7;
+
+        private static final String KEY_SERVICE_CLASS_NAME = "serviceclassname";
+
+        public ForegroundServiceDidNotStopInTimeException(String msg, Throwable cause) {
+            super(msg, cause);
+        }
+
+        public static Bundle createExtrasForService(@NonNull ComponentName service) {
+            Bundle b = new Bundle();
+            b.putString(KEY_SERVICE_CLASS_NAME, service.getClassName());
+            return b;
+        }
+
+        @Nullable
+        public static String getServiceClassNameFromExtras(@Nullable Bundle extras) {
+            return (extras == null) ? null : extras.getString(KEY_SERVICE_CLASS_NAME);
+        }
+    }
+
+    /**
      * Exception used to crash an app process when the system received a RemoteException
      * while posting a notification of a foreground service.
      *
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 726064e..aaddaa6 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -1198,8 +1198,7 @@
      * Callback called when a particular foreground service type has timed out.
      *
      * <p>This callback is meant to give the app a small grace period of a few seconds to finish
-     * the foreground service of the associated type - if it fails to do so, the app will be
-     * declared an ANR.
+     * the foreground service of the associated type - if it fails to do so, the app will crash.
      *
      * <p>The foreground service of the associated type can be stopped within the time limit by
      * {@link android.app.Service#stopSelf()},
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 301fef8..14195c4 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -21,7 +21,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
-import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -44,7 +43,6 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
-import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -59,7 +57,6 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.IUndoMediaTransferCallback;
 import com.android.internal.statusbar.NotificationVisibility;
-import com.android.internal.util.DataClass;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -632,49 +629,38 @@
     }
 
     /**
-     * @deprecated
-     * Disable some features in the status bar.  Pass the bitwise-or of the DISABLE_*
-     * flags. To re-enable everything, pass {@link #DISABLE_NONE}.
-     *
-     * This method is deprecated and callers should use
-     * {@link #requestDisabledComponent(DisableInfo, String)}
+     * Disable some features in the status bar.  Pass the bitwise-or of the DISABLE_* flags.
+     * To re-enable everything, pass {@link #DISABLE_NONE}.
      *
      * @hide
      */
-    @Deprecated
     @UnsupportedAppUsage
     public void disable(int what) {
-        requestDisabledComponent(new DisableInfo(what & DISABLE_MASK, what & DISABLE2_MASK), null);
-    }
-
-    /**
-     * @deprecated
-     * Disable some features in the status bar.  Pass the bitwise-or of the DISABLE_2*
-     * flags. To re-enable everything, pass {@link #DISABLE2_NONE}.
-     *
-     * This method is deprecated and callers should use
-     * {@link #requestDisabledComponent(DisableInfo, String)}
-     *
-     * @hide
-     */
-    @Deprecated
-    @UnsupportedAppUsage
-    public void disable2(int what) {
-        requestDisabledComponent(new DisableInfo(what & DISABLE_MASK, what & DISABLE2_MASK), null);
-    }
-
-    /**
-     * Disable some features in the status bar. Pass a DisableInfo object with the required flags.
-     *
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public void requestDisabledComponent(DisableInfo disableInfo, String reason) {
         try {
             final int userId = Binder.getCallingUserHandle().getIdentifier();
             final IStatusBarService svc = getService();
             if (svc != null) {
-                svc.disableForUser(disableInfo, mToken, mContext.getPackageName(), userId, reason);
+                svc.disableForUser(what, mToken, mContext.getPackageName(), userId);
+            }
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Disable additional status bar features. Pass the bitwise-or of the DISABLE2_* flags.
+     * To re-enable everything, pass {@link #DISABLE_NONE}.
+     *
+     * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags.
+     *
+     * @hide
+     */
+    public void disable2(@Disable2Flags int what) {
+        try {
+            final int userId = Binder.getCallingUserHandle().getIdentifier();
+            final IStatusBarService svc = getService();
+            if (svc != null) {
+                svc.disable2ForUser(what, mToken, mContext.getPackageName(), userId);
             }
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
@@ -902,9 +888,18 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.STATUS_BAR)
     public void setDisabledForSetup(boolean disabled) {
-        int flags1 = disabled ? DEFAULT_SETUP_DISABLE_FLAGS : DISABLE_NONE;
-        DisableInfo info = new DisableInfo(flags1, DISABLE2_NONE);
-        requestDisabledComponent(info, "setDisabledForSetup");
+        try {
+            final int userId = Binder.getCallingUserHandle().getIdentifier();
+            final IStatusBarService svc = getService();
+            if (svc != null) {
+                svc.disableForUser(disabled ? DEFAULT_SETUP_DISABLE_FLAGS : DISABLE_NONE,
+                        mToken, mContext.getPackageName(), userId);
+                svc.disable2ForUser(disabled ? DEFAULT_SETUP_DISABLE2_FLAGS : DISABLE2_NONE,
+                        mToken, mContext.getPackageName(), userId);
+            }
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -919,9 +914,16 @@
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @RequiresPermission(android.Manifest.permission.STATUS_BAR)
     public void setExpansionDisabledForSimNetworkLock(boolean disabled) {
-        int flags1 = disabled ? DEFAULT_SIM_LOCKED_DISABLED_FLAGS : DISABLE_NONE;
-        DisableInfo info = new DisableInfo(flags1, DISABLE2_NONE);
-        requestDisabledComponent(info, "setExpansionDisabledForSimNetworkLock");
+        try {
+            final int userId = Binder.getCallingUserHandle().getIdentifier();
+            final IStatusBarService svc = getService();
+            if (svc != null) {
+                svc.disableForUser(disabled ? DEFAULT_SIM_LOCKED_DISABLED_FLAGS : DISABLE_NONE,
+                        mToken, mContext.getPackageName(), userId);
+            }
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -1313,75 +1315,33 @@
      * @hide
      */
     @SystemApi
-    @DataClass
-    public static final class DisableInfo implements Parcelable {
+    public static final class DisableInfo {
 
-        /**
-         * @hide
-         */
         private boolean mStatusBarExpansion;
-        /**
-         * @hide
-         */
         private boolean mNavigateHome;
-        /**
-         * @hide
-         */
         private boolean mNotificationPeeking;
-        /**
-         * @hide
-         */
         private boolean mRecents;
-        /**
-         * @hide
-         */
-        private boolean mBack;
-        /**
-         * @hide
-         */
         private boolean mSearch;
-        /**
-         * @hide
-         */
         private boolean mSystemIcons;
-        /**
-         * @hide
-         */
         private boolean mClock;
-        /**
-         * @hide
-         */
         private boolean mNotificationIcons;
-        /**
-         * @hide
-         */
         private boolean mRotationSuggestion;
-        /**
-         * @hide
-         */
-        private boolean mNotificationTicker;
 
         /** @hide */
-        @SuppressLint("UnflaggedApi")
         public DisableInfo(int flags1, int flags2) {
             mStatusBarExpansion = (flags1 & DISABLE_EXPAND) != 0;
             mNavigateHome = (flags1 & DISABLE_HOME) != 0;
             mNotificationPeeking = (flags1 & DISABLE_NOTIFICATION_ALERTS) != 0;
             mRecents = (flags1 & DISABLE_RECENT) != 0;
-            mBack = (flags1 & DISABLE_BACK) != 0;
             mSearch = (flags1 & DISABLE_SEARCH) != 0;
             mSystemIcons = (flags1 & DISABLE_SYSTEM_INFO) != 0;
             mClock = (flags1 & DISABLE_CLOCK) != 0;
             mNotificationIcons = (flags1 & DISABLE_NOTIFICATION_ICONS) != 0;
-            mNotificationTicker = (flags1 & DISABLE_NOTIFICATION_TICKER) != 0;
             mRotationSuggestion = (flags2 & DISABLE2_ROTATE_SUGGESTIONS) != 0;
         }
 
         /** @hide */
-        @SuppressLint("UnflaggedApi")
-        public DisableInfo() {
-            setEnableAll();
-        }
+        public DisableInfo() {}
 
         /**
          * @return {@code true} if expanding the notification shade is disabled
@@ -1409,7 +1369,7 @@
         }
 
         /** * @hide */
-        public void setNavigationHomeDisabled(boolean disabled) {
+        public void setNagivationHomeDisabled(boolean disabled) {
             mNavigateHome = disabled;
         }
 
@@ -1444,20 +1404,6 @@
         }
 
         /**
-         * @return {@code true} if mBack is disabled
-         *
-         * @hide
-         */
-        public boolean isBackDisabled() {
-            return mBack;
-        }
-
-        /**  @hide */
-        public void setBackDisabled(boolean disabled) {
-            mBack = disabled;
-        }
-
-        /**
          * @return {@code true} if mSearch is disabled
          *
          * @hide
@@ -1515,20 +1461,6 @@
         }
 
         /**
-         * @return {@code true} if notification ticker is disabled
-         *
-         * @hide
-         */
-        public boolean isNotificationTickerDisabled() {
-            return mNotificationTicker;
-        }
-
-        /** * @hide */
-        public void setNotificationTickerDisabled(boolean disabled) {
-            mNotificationTicker = disabled;
-        }
-
-        /**
          * Returns whether the rotation suggestion is disabled.
          *
          * @hide
@@ -1538,11 +1470,6 @@
             return mRotationSuggestion;
         }
 
-        /** * @hide */
-        public void setRotationSuggestionDisabled(boolean disabled) {
-            mNotificationIcons = disabled;
-        }
-
         /**
          * @return {@code true} if no components are disabled (default state)
          * @hide
@@ -1550,8 +1477,8 @@
         @SystemApi
         public boolean areAllComponentsEnabled() {
             return !mStatusBarExpansion && !mNavigateHome && !mNotificationPeeking && !mRecents
-                    && !mBack && !mSearch && !mSystemIcons && !mClock && !mNotificationIcons
-                    && !mNotificationTicker && !mRotationSuggestion;
+                    && !mSearch && !mSystemIcons && !mClock && !mNotificationIcons
+                    && !mRotationSuggestion;
         }
 
         /** @hide */
@@ -1560,12 +1487,10 @@
             mNavigateHome = false;
             mNotificationPeeking = false;
             mRecents = false;
-            mBack = false;
             mSearch = false;
             mSystemIcons = false;
             mClock = false;
             mNotificationIcons = false;
-            mNotificationTicker = false;
             mRotationSuggestion = false;
         }
 
@@ -1575,9 +1500,9 @@
          * @hide
          */
         public boolean areAllComponentsDisabled() {
-            return mStatusBarExpansion && mNavigateHome && mNotificationPeeking && mRecents && mBack
-                    && mSearch && mSystemIcons && mClock && mNotificationIcons
-                    && mNotificationTicker && mRotationSuggestion;
+            return mStatusBarExpansion && mNavigateHome && mNotificationPeeking
+                    && mRecents && mSearch && mSystemIcons && mClock && mNotificationIcons
+                    && mRotationSuggestion;
         }
 
         /** @hide */
@@ -1586,12 +1511,10 @@
             mNavigateHome = true;
             mNotificationPeeking = true;
             mRecents = true;
-            mBack = true;
             mSearch = true;
             mSystemIcons = true;
             mClock = true;
             mNotificationIcons = true;
-            mNotificationTicker = true;
             mRotationSuggestion = true;
         }
 
@@ -1599,19 +1522,16 @@
         @Override
         public String toString() {
             StringBuilder sb = new StringBuilder();
-
-            sb.append("Disable Info: ");
+            sb.append("DisableInfo: ");
             sb.append(" mStatusBarExpansion=").append(mStatusBarExpansion ? "disabled" : "enabled");
             sb.append(" mNavigateHome=").append(mNavigateHome ? "disabled" : "enabled");
             sb.append(" mNotificationPeeking=")
                     .append(mNotificationPeeking ? "disabled" : "enabled");
             sb.append(" mRecents=").append(mRecents ? "disabled" : "enabled");
-            sb.append(" mBack=").append(mBack ? "disabled" : "enabled");
             sb.append(" mSearch=").append(mSearch ? "disabled" : "enabled");
             sb.append(" mSystemIcons=").append(mSystemIcons ? "disabled" : "enabled");
             sb.append(" mClock=").append(mClock ? "disabled" : "enabled");
             sb.append(" mNotificationIcons=").append(mNotificationIcons ? "disabled" : "enabled");
-            sb.append(" mNotificationTicker=").append(mNotificationTicker ? "disabled" : "enabled");
             sb.append(" mRotationSuggestion=").append(mRotationSuggestion ? "disabled" : "enabled");
 
             return sb.toString();
@@ -1619,7 +1539,7 @@
         }
 
         /**
-         * Convert a DisableInfo to equivalent flags.
+         * Convert a DisableInfo to equivalent flags
          * @return a pair of equivalent disable flags
          *
          * @hide
@@ -1632,278 +1552,14 @@
             if (mNavigateHome) disable1 |= DISABLE_HOME;
             if (mNotificationPeeking) disable1 |= DISABLE_NOTIFICATION_ALERTS;
             if (mRecents) disable1 |= DISABLE_RECENT;
-            if (mBack) disable1 |= DISABLE_BACK;
             if (mSearch) disable1 |= DISABLE_SEARCH;
             if (mSystemIcons) disable1 |= DISABLE_SYSTEM_INFO;
             if (mClock) disable1 |= DISABLE_CLOCK;
             if (mNotificationIcons) disable1 |= DISABLE_NOTIFICATION_ICONS;
-            if (mNotificationTicker) disable1 |= DISABLE_NOTIFICATION_TICKER;
             if (mRotationSuggestion) disable2 |= DISABLE2_ROTATE_SUGGESTIONS;
 
             return new Pair<Integer, Integer>(disable1, disable2);
         }
-
-
-
-        // Code below generated by codegen v1.0.23.
-        //
-        // DO NOT MODIFY!
-        // CHECKSTYLE:OFF Generated code
-        //
-        // To regenerate run:
-        // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/StatusBarManager.java
-        //
-        // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
-        //   Settings > Editor > Code Style > Formatter Control
-        //@formatter:off
-
-
-        /**
-         * Creates a new DisableInfo.
-         * @hide
-         */
-        @DataClass.Generated.Member
-        public DisableInfo(
-                boolean statusBarExpansion,
-                boolean navigateHome,
-                boolean notificationPeeking,
-                boolean recents,
-                boolean back,
-                boolean search,
-                boolean systemIcons,
-                boolean clock,
-                boolean notificationIcons,
-                boolean rotationSuggestion,
-                boolean notificationTicker) {
-            this.mStatusBarExpansion = statusBarExpansion;
-            this.mNavigateHome = navigateHome;
-            this.mNotificationPeeking = notificationPeeking;
-            this.mRecents = recents;
-            this.mBack = back;
-            this.mSearch = search;
-            this.mSystemIcons = systemIcons;
-            this.mClock = clock;
-            this.mNotificationIcons = notificationIcons;
-            this.mRotationSuggestion = rotationSuggestion;
-            this.mNotificationTicker = notificationTicker;
-
-            // onConstructed(); // You can define this method to get a callback
-        }
-
-        /**
-         * @hide
-         */
-        @DataClass.Generated.Member
-        public boolean isStatusBarExpansion() {
-            return mStatusBarExpansion;
-        }
-
-        /**
-         * @hide
-         */
-        @DataClass.Generated.Member
-        public boolean isNavigateHome() {
-            return mNavigateHome;
-        }
-
-        /**
-         * @hide
-         */
-        @DataClass.Generated.Member
-        public boolean isNotificationPeeking() {
-            return mNotificationPeeking;
-        }
-
-        /**
-         * @hide
-         */
-        @DataClass.Generated.Member
-        public boolean isRecents() {
-            return mRecents;
-        }
-
-        /**
-         * @hide
-         */
-        @DataClass.Generated.Member
-        public boolean isBack() {
-            return mBack;
-        }
-
-        /**
-         * @hide
-         */
-        @DataClass.Generated.Member
-        public boolean isSearch() {
-            return mSearch;
-        }
-
-        /**
-         * @hide
-         */
-        @DataClass.Generated.Member
-        public boolean isSystemIcons() {
-            return mSystemIcons;
-        }
-
-        /**
-         * @hide
-         */
-        @DataClass.Generated.Member
-        public boolean isClock() {
-            return mClock;
-        }
-
-        /**
-         * @hide
-         */
-        @DataClass.Generated.Member
-        public boolean isNotificationIcons() {
-            return mNotificationIcons;
-        }
-
-        /**
-         * @hide
-         */
-        @DataClass.Generated.Member
-        public boolean isRotationSuggestion() {
-            return mRotationSuggestion;
-        }
-
-        /**
-         * @hide
-         */
-        @DataClass.Generated.Member
-        public boolean isNotificationTicker() {
-            return mNotificationTicker;
-        }
-
-        /**
-         * @hide
-         */
-        @SuppressLint("UnflaggedApi")
-        @Override
-        @DataClass.Generated.Member
-        public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
-            // You can override field parcelling by defining methods like:
-            // void parcelFieldName(Parcel dest, int flags) { ... }
-
-            int flg = 0;
-            if (mStatusBarExpansion) flg |= 0x1;
-            if (mNavigateHome) flg |= 0x2;
-            if (mNotificationPeeking) flg |= 0x4;
-            if (mRecents) flg |= 0x8;
-            if (mBack) flg |= 0x10;
-            if (mSearch) flg |= 0x20;
-            if (mSystemIcons) flg |= 0x40;
-            if (mClock) flg |= 0x80;
-            if (mNotificationIcons) flg |= 0x100;
-            if (mRotationSuggestion) flg |= 0x200;
-            if (mNotificationTicker) flg |= 0x400;
-            dest.writeInt(flg);
-        }
-
-        /**
-         * @hide
-         */
-        @SuppressLint("UnflaggedApi")
-        @Override
-        @DataClass.Generated.Member
-        public int describeContents() { return 0; }
-
-        /** @hide */
-        @SuppressWarnings({"unchecked", "RedundantCast"})
-        @DataClass.Generated.Member
-        /* package-private */ DisableInfo(@NonNull android.os.Parcel in) {
-            // You can override field unparcelling by defining methods like:
-            // static FieldType unparcelFieldName(Parcel in) { ... }
-
-            int flg = in.readInt();
-            boolean statusBarExpansion = (flg & 0x1) != 0;
-            boolean navigateHome = (flg & 0x2) != 0;
-            boolean notificationPeeking = (flg & 0x4) != 0;
-            boolean recents = (flg & 0x8) != 0;
-            boolean back = (flg & 0x10) != 0;
-            boolean search = (flg & 0x20) != 0;
-            boolean systemIcons = (flg & 0x40) != 0;
-            boolean clock = (flg & 0x80) != 0;
-            boolean notificationIcons = (flg & 0x100) != 0;
-            boolean rotationSuggestion = (flg & 0x200) != 0;
-            boolean notificationTicker = (flg & 0x400) != 0;
-
-            this.mStatusBarExpansion = statusBarExpansion;
-            this.mNavigateHome = navigateHome;
-            this.mNotificationPeeking = notificationPeeking;
-            this.mRecents = recents;
-            this.mBack = back;
-            this.mSearch = search;
-            this.mSystemIcons = systemIcons;
-            this.mClock = clock;
-            this.mNotificationIcons = notificationIcons;
-            this.mRotationSuggestion = rotationSuggestion;
-            this.mNotificationTicker = notificationTicker;
-
-            // onConstructed(); // You can define this method to get a callback
-        }
-
-        @DataClass.Generated.Member
-        public static final @NonNull Parcelable.Creator<DisableInfo> CREATOR
-                = new Parcelable.Creator<DisableInfo>() {
-            @Override
-            public DisableInfo[] newArray(int size) {
-                return new DisableInfo[size];
-            }
-
-            @Override
-            public DisableInfo createFromParcel(@NonNull android.os.Parcel in) {
-                return new DisableInfo(in);
-            }
-        };
-
-        @DataClass.Generated(
-                time = 1708625947132L,
-                codegenVersion = "1.0.23",
-                sourceFile = "frameworks/base/core/java/android/app/StatusBarManager.java",
-                inputSignatures = "private  boolean mStatusBarExpansion\nprivate  boolean "
-                        + "mNavigateHome\nprivate  boolean mNotificationPeeking\nprivate  "
-                        + "boolean mRecents\nprivate  boolean mBack\nprivate  boolean mSearch\n"
-                        + "private  boolean mSystemIcons\nprivate  boolean mClock\nprivate  "
-                        + "boolean mNotificationIcons\nprivate  boolean mRotationSuggestion\n"
-                        + "private  boolean mNotificationTicker\npublic "
-                        + "@android.annotation.SystemApi boolean isStatusBarExpansionDisabled()\n"
-                        + "public  void setStatusBarExpansionDisabled(boolean)\npublic "
-                        + "@android.annotation.SystemApi boolean isNavigateToHomeDisabled()\npublic"
-                        + "  void setNavigationHomeDisabled(boolean)\npublic "
-                        + "@android.annotation.SystemApi boolean isNotificationPeekingDisabled()"
-                        + "\npublic  void setNotificationPeekingDisabled(boolean)\npublic "
-                        + "@android.annotation.SystemApi boolean isRecentsDisabled()\npublic  "
-                        + "void setRecentsDisabled(boolean)\npublic  boolean isBackDisabled()"
-                        + "\npublic  void setBackDisabled(boolean)\npublic "
-                        + "@android.annotation.SystemApi boolean isSearchDisabled()\npublic  "
-                        + "void setSearchDisabled(boolean)\npublic  boolean "
-                        + "areSystemIconsDisabled()\npublic  void setSystemIconsDisabled(boolean)\n"
-                        + "public  boolean isClockDisabled()\npublic  "
-                        + "void setClockDisabled(boolean)\npublic  boolean "
-                        + "areNotificationIconsDisabled()\npublic  void "
-                        + "setNotificationIconsDisabled(boolean)\npublic  boolean "
-                        + "isNotificationTickerDisabled()\npublic  void "
-                        + "setNotificationTickerDisabled(boolean)\npublic "
-                        + "@android.annotation.TestApi boolean isRotationSuggestionDisabled()\n"
-                        + "public  void setRotationSuggestionDisabled(boolean)\npublic "
-                        + "@android.annotation.SystemApi boolean areAllComponentsEnabled()\npublic"
-                        + "  void setEnableAll()\npublic  boolean areAllComponentsDisabled()\n"
-                        + "public  void setDisableAll()\npublic @android.annotation.NonNull "
-                        + "@java.lang.Override java.lang.String toString()\npublic  "
-                        + "android.util.Pair<java.lang.Integer,java.lang.Integer> toFlags()\n"
-                        + "class DisableInfo extends java.lang.Object implements "
-                        + "[android.os.Parcelable]\n@com.android.internal.util.DataClass")
-        @Deprecated
-        private void __metadata() {}
-
-
-        //@formatter:on
-        // End of generated code
-
     }
 
     /**
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index a29c196..0deb842 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -378,6 +378,14 @@
                 }
             ],
             "file_patterns": ["(/|^)ContextImpl.java"]
+        },
+        {
+            "file_patterns": [
+                "(/|^)Activity.*.java",
+                "(/|^)PendingIntent.java",
+                "(/|^)ComtextImpl.java"
+            ],
+            "name": "CtsWindowManagerBackgroundActivityTestCases"
         }
     ],
     "postsubmit": [
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index efd5a45..ef8501f 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -351,6 +351,12 @@
     }
 
     /** @hide */
+    public boolean isFreeform() {
+        return configuration.windowConfiguration.getWindowingMode()
+                == WindowConfiguration.WINDOWING_MODE_FREEFORM;
+    }
+
+    /** @hide */
     @WindowConfiguration.ActivityType
     public int getActivityType() {
         return configuration.windowConfiguration.getActivityType();
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index 8c4667f..bb24fd1 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -50,3 +50,23 @@
          purpose: PURPOSE_BUGFIX
      }
 }
+
+flag {
+     namespace: "backstage_power"
+     name: "gate_fgs_timeout_anr_behavior"
+     description: "Gate the new behavior where an ANR is thrown once an FGS times out."
+     bug: "339315145"
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
+}
+
+flag {
+     namespace: "backstage_power"
+     name: "enable_fgs_timeout_crash_behavior"
+     description: "Enable the new behavior where the app is crashed once an FGS times out."
+     bug: "339526947"
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
+}
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 9ef8b38..46c9e78 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -21,6 +21,7 @@
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.app.admin.flags.Flags;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
@@ -176,6 +177,10 @@
      * provisioned into "affiliated" mode when on a Headless System User Mode device.
      *
      * <p>This mode adds a Profile Owner to all users other than the user the Device Owner is on.
+     *
+     * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
+     * DPCs should set the value of attribute "headless-device-owner-mode" inside the
+     * "headless-system-user" tag as "affiliated".
      */
     public static final int HEADLESS_DEVICE_OWNER_MODE_AFFILIATED = 1;
 
@@ -185,6 +190,10 @@
      *
      * <p>This mode only allows a single secondary user on the device blocking the creation of
      * additional secondary users.
+     *
+     * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
+     * DPCs should set the value of attribute "headless-device-owner-mode" inside the
+     * "headless-system-user" tag as "single_user".
      */
     @FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
     public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2;
@@ -383,17 +392,30 @@
                     }
                     mSupportsTransferOwnership = true;
                 } else if (tagName.equals("headless-system-user")) {
-                    String deviceOwnerModeStringValue =
-                            parser.getAttributeValue(null, "device-owner-mode");
+                    String deviceOwnerModeStringValue = null;
+                    if (Flags.headlessSingleUserCompatibilityFix()) {
+                        deviceOwnerModeStringValue = parser.getAttributeValue(
+                                 null, "headless-device-owner-mode");
+                    }
+                    if (deviceOwnerModeStringValue == null) {
+                        deviceOwnerModeStringValue =
+                                parser.getAttributeValue(null, "device-owner-mode");
+                    }
 
-                    if (deviceOwnerModeStringValue.equalsIgnoreCase("unsupported")) {
+                    if ("unsupported".equalsIgnoreCase(deviceOwnerModeStringValue)) {
                         mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
-                    } else if (deviceOwnerModeStringValue.equalsIgnoreCase("affiliated")) {
+                    } else if ("affiliated".equalsIgnoreCase(deviceOwnerModeStringValue)) {
                         mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
-                    } else if (deviceOwnerModeStringValue.equalsIgnoreCase("single_user")) {
+                    } else if ("single_user".equalsIgnoreCase(deviceOwnerModeStringValue)) {
                         mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
                     } else {
-                        throw new XmlPullParserException("headless-system-user mode must be valid");
+                        if (Flags.headlessSingleUserCompatibilityFix()) {
+                            Log.e(TAG, "Unknown headless-system-user mode: "
+                                    + deviceOwnerModeStringValue);
+                        } else {
+                            throw new XmlPullParserException(
+                                    "headless-system-user mode must be valid");
+                        }
                     }
                 }
             }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 69f29f3..c529f7d 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1698,7 +1698,7 @@
 
     /**
      * A boolean extra indicating whether device encryption can be skipped as part of
-     * <a href="#managed-provisioning>provisioning</a>.
+     * <a href="#managed-provisioning">provisioning</a>.
      *
      * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} or an intent with action
      * {@link #ACTION_PROVISION_MANAGED_DEVICE} that starts device owner provisioning.
@@ -10427,7 +10427,7 @@
     @WorkerThread
     public void setApplicationRestrictions(@Nullable ComponentName admin, String packageName,
             Bundle settings) {
-        if (!Flags.dmrhCanSetAppRestriction()) {
+        if (!Flags.dmrhSetAppRestrictions()) {
             throwIfParentInstance("setApplicationRestrictions");
         }
 
@@ -11835,7 +11835,7 @@
     @WorkerThread
     public @NonNull Bundle getApplicationRestrictions(
             @Nullable ComponentName admin, String packageName) {
-        if (!Flags.dmrhCanSetAppRestriction()) {
+        if (!Flags.dmrhSetAppRestrictions()) {
             throwIfParentInstance("getApplicationRestrictions");
         }
 
@@ -14120,7 +14120,7 @@
     public @NonNull DevicePolicyManager getParentProfileInstance(@NonNull ComponentName admin) {
         throwIfParentInstance("getParentProfileInstance");
         try {
-            if (Flags.dmrhCanSetAppRestriction()) {
+            if (Flags.dmrhSetAppRestrictions()) {
                 UserManager um = mContext.getSystemService(UserManager.class);
                 if (!um.isManagedProfile()) {
                     throw new SecurityException("The current user does not have a parent profile.");
diff --git a/core/java/android/app/admin/StringSetPolicyValue.java b/core/java/android/app/admin/PackageSetPolicyValue.java
similarity index 71%
rename from core/java/android/app/admin/StringSetPolicyValue.java
rename to core/java/android/app/admin/PackageSetPolicyValue.java
index 12b11f4..8b253a2 100644
--- a/core/java/android/app/admin/StringSetPolicyValue.java
+++ b/core/java/android/app/admin/PackageSetPolicyValue.java
@@ -28,18 +28,18 @@
 /**
  * @hide
  */
-public final class StringSetPolicyValue extends PolicyValue<Set<String>> {
+public final class PackageSetPolicyValue extends PolicyValue<Set<String>> {
 
-    public StringSetPolicyValue(@NonNull Set<String> value) {
+    public PackageSetPolicyValue(@NonNull Set<String> value) {
         super(value);
         if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-            for (String str : value) {
-                PolicySizeVerifier.enforceMaxStringLength(str, "policyValue");
+            for (String packageName : value) {
+                PolicySizeVerifier.enforceMaxPackageNameLength(packageName);
             }
         }
     }
 
-    public StringSetPolicyValue(Parcel source) {
+    public PackageSetPolicyValue(Parcel source) {
         this(readValues(source));
     }
 
@@ -56,7 +56,7 @@
     public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
-        StringSetPolicyValue other = (StringSetPolicyValue) o;
+        PackageSetPolicyValue other = (PackageSetPolicyValue) o;
         return Objects.equals(getValue(), other.getValue());
     }
 
@@ -67,7 +67,7 @@
 
     @Override
     public String toString() {
-        return "StringSetPolicyValue { " + getValue() + " }";
+        return "PackageNameSetPolicyValue { " + getValue() + " }";
     }
 
     @Override
@@ -84,16 +84,16 @@
     }
 
     @NonNull
-    public static final Creator<StringSetPolicyValue> CREATOR =
-            new Creator<StringSetPolicyValue>() {
+    public static final Creator<PackageSetPolicyValue> CREATOR =
+            new Creator<PackageSetPolicyValue>() {
                 @Override
-                public StringSetPolicyValue createFromParcel(Parcel source) {
-                    return new StringSetPolicyValue(source);
+                public PackageSetPolicyValue createFromParcel(Parcel source) {
+                    return new PackageSetPolicyValue(source);
                 }
 
                 @Override
-                public StringSetPolicyValue[] newArray(int size) {
-                    return new StringSetPolicyValue[size];
+                public PackageSetPolicyValue[] newArray(int size) {
+                    return new PackageSetPolicyValue[size];
                 }
             };
 }
diff --git a/core/java/android/app/admin/SystemUpdatePolicy.java b/core/java/android/app/admin/SystemUpdatePolicy.java
index 7320cea..dede5b5 100644
--- a/core/java/android/app/admin/SystemUpdatePolicy.java
+++ b/core/java/android/app/admin/SystemUpdatePolicy.java
@@ -78,6 +78,11 @@
  *
  * <h3>Developer guide</h3>
  * To learn more, read <a href="{@docRoot}work/dpc/system-updates">Manage system updates</a>.
+ * <p><strong>Note:</strong> <a href="https://source.android.com/docs/core/ota/modular-system">
+ * Google Play system updates</a> (also called Mainline updates) are automatically downloaded
+ * but require a device reboot to be installed. Refer to the mainline section in
+ * <a href="{@docRoot}work/dpc/system-updates#mainline">Manage system
+ * updates</a> for further details.</p>
  *
  * @see DevicePolicyManager#setSystemUpdatePolicy
  * @see DevicePolicyManager#getSystemUpdatePolicy
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 18914e1..4154e66 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -217,6 +217,16 @@
 }
 
 flag {
+  name: "disallow_user_control_stopped_state_fix"
+  namespace: "enterprise"
+  description: "Ensure DPM.setUserControlDisabledPackages() clears FLAG_STOPPED for the app"
+  bug: "330688482"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "esim_management_ux_enabled"
   namespace: "enterprise"
   description: "Enable UX changes for esim management"
@@ -234,10 +244,13 @@
 }
 
 flag {
-  name: "dmrh_can_set_app_restriction"
+  name: "dmrh_set_app_restrictions"
   namespace: "enterprise"
   description: "Allow DMRH to set application restrictions (both on the profile and the parent)"
   bug: "328758346"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
 }
 
 flag {
@@ -303,3 +316,23 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "headless_single_user_compatibility_fix"
+    namespace: "enterprise"
+    description: "Fix for compatibility issue introduced from using single_user mode on pre-Android V builds"
+    bug: "338050276"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
+    name: "headless_single_min_target_sdk"
+    namespace: "enterprise"
+    description: "Only allow DPCs targeting Android V to provision into single user mode"
+    bug: "338588825"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 63ffaa0..6ceae17 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -60,6 +60,13 @@
 }
 
 flag {
+  name: "notification_expansion_optional"
+  namespace: "systemui"
+  description: "Experiment to restore the pre-S behavior where standard notifications are not expandable unless they have actions."
+  bug: "339523906"
+}
+
+flag {
   name: "keyguard_private_notifications"
   namespace: "systemui"
   description: "Fixes the behavior of KeyguardManager#setPrivateNotificationsAllowed()"
@@ -153,3 +160,10 @@
   description: "[Minimal HUN] Enables the compact heads up notification feature"
   bug: "270709257"
 }
+
+flag {
+  name: "compact_heads_up_notification_reply"
+  namespace: "systemui"
+  description: "[Minimal HUN] Enables the compact heads up notification reply capability for Conversation Notifications"
+  bug: "336229954"
+}
\ No newline at end of file
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
index 5e1c1e0..a37f51b 100644
--- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@@ -529,7 +529,6 @@
      * {@link Bundle}s annotated with this type will be validated that they are in-effect read-only
      * when passed via Binder IPC. Following restrictions apply :
      * <ul>
-     * <li> No Nested Bundles are allowed.</li>
      * <li> {@link PersistableBundle}s are allowed.</li>
      * <li> Any primitive types or their collections can be added as usual.</li>
      * <li>IBinder objects should *not* be added.</li>
diff --git a/core/java/android/app/prediction/AppPredictionContext.java b/core/java/android/app/prediction/AppPredictionContext.java
index 99fa869..1b718d4 100644
--- a/core/java/android/app/prediction/AppPredictionContext.java
+++ b/core/java/android/app/prediction/AppPredictionContext.java
@@ -24,6 +24,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Objects;
+
 /**
  * Class that provides contextual information about the environment in which the app prediction is
  * used, such as package name, UI in which the app targets are shown, and number of targets.
@@ -99,6 +101,13 @@
     }
 
     @Override
+    public int hashCode() {
+        int hashCode = Objects.hash(mUiSurface, mPackageName);
+        hashCode = 31 * hashCode + mPredictedTargetCount;
+        return hashCode;
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
diff --git a/core/java/android/app/prediction/AppTarget.java b/core/java/android/app/prediction/AppTarget.java
index fef9e70..25c1a59 100644
--- a/core/java/android/app/prediction/AppTarget.java
+++ b/core/java/android/app/prediction/AppTarget.java
@@ -167,6 +167,16 @@
     }
 
     @Override
+    public int hashCode() {
+        int hashCode = Objects.hash(mId, mPackageName, mClassName, mUser);
+        if (mShortcutInfo != null) {
+            hashCode = 31 * hashCode + mShortcutInfo.getId().hashCode();
+        }
+        hashCode = 31 * hashCode + mRank;
+        return hashCode;
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
diff --git a/core/java/android/app/prediction/AppTargetEvent.java b/core/java/android/app/prediction/AppTargetEvent.java
index 91da8ec..e36d878 100644
--- a/core/java/android/app/prediction/AppTargetEvent.java
+++ b/core/java/android/app/prediction/AppTargetEvent.java
@@ -24,6 +24,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
 
 /**
  * A representation of an app target event.
@@ -116,6 +117,13 @@
     }
 
     @Override
+    public int hashCode() {
+        int hashCode = Objects.hash(mTarget, mLocation);
+        hashCode = 31 * hashCode + mAction;
+        return hashCode;
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
diff --git a/core/java/android/app/prediction/OWNERS b/core/java/android/app/prediction/OWNERS
index fe012da..73168fb 100644
--- a/core/java/android/app/prediction/OWNERS
+++ b/core/java/android/app/prediction/OWNERS
@@ -1,2 +1,4 @@
+pinyaoting@google.com
+hyunyoungs@google.com
 adamcohen@google.com
 sunnygoyal@google.com
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index cda2867..9b53461 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -33,11 +33,13 @@
 import android.os.IBinder;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Log;
 import android.window.ActivityWindowInfo;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.concurrent.RejectedExecutionException;
 import java.util.function.BiConsumer;
 
 /**
@@ -47,6 +49,8 @@
  */
 public class ClientTransactionListenerController {
 
+    private static final String TAG = "ClientTransactionListenerController";
+
     private static ClientTransactionListenerController sController;
 
     private final Object mLock = new Object();
@@ -179,10 +183,14 @@
         }
 
         // Dispatch the display changed callbacks.
-        final int displayCount = configUpdatedDisplayIds.size();
-        for (int i = 0; i < displayCount; i++) {
-            final int displayId = configUpdatedDisplayIds.valueAt(i);
-            onDisplayChanged(displayId);
+        try {
+            final int displayCount = configUpdatedDisplayIds.size();
+            for (int i = 0; i < displayCount; i++) {
+                final int displayId = configUpdatedDisplayIds.valueAt(i);
+                onDisplayChanged(displayId);
+            }
+        } catch (RejectedExecutionException e) {
+            Log.w(TAG, "Failed to notify DisplayListener because the Handler is shutting down");
         }
     }
 
@@ -222,7 +230,11 @@
         }
 
         if (changedDisplayId != INVALID_DISPLAY) {
-            onDisplayChanged(changedDisplayId);
+            try {
+                onDisplayChanged(changedDisplayId);
+            } catch (RejectedExecutionException e) {
+                Log.w(TAG, "Failed to notify DisplayListener because the Handler is shutting down");
+            }
         }
     }
 
@@ -235,9 +247,11 @@
     /**
      * Called when receives a {@link Configuration} changed event that is updating display-related
      * window configuration.
+     *
+     * @throws RejectedExecutionException if the display listener handler is closing.
      */
     @VisibleForTesting
-    public void onDisplayChanged(int displayId) {
+    public void onDisplayChanged(int displayId) throws RejectedExecutionException {
         mDisplayManager.handleDisplayChangeFromWindowManager(displayId);
     }
 }
diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig
index c7b168a..04c3686 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -47,3 +47,14 @@
     description: "Feature flag for collecting app data size by file type API"
     bug: "294088945"
 }
+
+flag {
+    name: "disable_idle_check"
+    namespace: "backstage_power"
+    description: "disable idle check for USER_SYSTEM during boot up"
+    is_fixed_read_only: true
+    bug: "337864590"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 57b5c13..3213b40 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -1041,6 +1041,7 @@
      */
     public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
         if (mService == null) {
+            Log.e(TAG, "Service wasn't initialized, appWidgetId=" + appWidgetId);
             return null;
         }
         try {
@@ -1048,6 +1049,9 @@
             if (info != null) {
                 // Converting complex to dp.
                 info.updateDimensions(mDisplayMetrics);
+            } else {
+                Log.e(TAG, "App widget provider info is null. PackageName=" + mPackageName
+                        + " appWidgetId-" + appWidgetId);
             }
             return info;
         } catch (RemoteException e) {
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index 8458857..36d0e08 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -39,3 +39,11 @@
     description: "Expose perm sync user consent API"
     bug: "309528663"
 }
+
+flag {
+    name: "ongoing_perm_sync"
+    is_exported: true
+    namespace: "companion"
+    description: "Enable ongoing perm sync"
+    bug: "338469649"
+}
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index 3e23762..b29b52d 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -127,4 +127,15 @@
   metadata {
     purpose: PURPOSE_BUGFIX
   }
+}
+
+flag {
+  name: "impulse_velocity_strategy_for_touch_navigation"
+  is_exported: true
+  namespace: "virtual_devices"
+  description: "Use impulse velocity strategy during conversion of touch navigation flings into Dpad events"
+  bug: "338426241"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
 }
\ No newline at end of file
diff --git a/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl b/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl
index 9f09d04..5a13255 100644
--- a/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl
+++ b/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl
@@ -48,6 +48,8 @@
     const int POLICY_TYPE_AUDIO = 1;
     const int POLICY_TYPE_RECENTS = 2;
     const int POLICY_TYPE_ACTIVITY = 3;
+    const int POLICY_TYPE_CLIPBOARD = 4;
+    const int POLICY_TYPE_CAMERA = 5;
 
     /**
      * Returns the IDs for all VirtualDevices where an app with the given is running.
@@ -62,4 +64,4 @@
      * Returns the device policy for the given virtual device and policy type.
      */
     int getDevicePolicy(int deviceId, int policyType);
-}
\ No newline at end of file
+}
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index af13011..37f419d 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -162,17 +162,6 @@
 
     /** @hide */
     @TestApi
-    @FlaggedApi(Flags.FLAG_ATTRIBUTION_SOURCE_CONSTRUCTOR)
-    public AttributionSource(int uid, int pid, @Nullable String packageName,
-            @Nullable String attributionTag, @NonNull IBinder token,
-            @Nullable String[] renouncedPermissions,
-            @Nullable AttributionSource next) {
-        this(uid, pid, packageName, attributionTag, token, renouncedPermissions,
-                Context.DEVICE_ID_DEFAULT, next);
-    }
-
-    /** @hide */
-    @TestApi
     @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
     public AttributionSource(int uid, int pid, @Nullable String packageName,
             @Nullable String attributionTag, @NonNull IBinder token,
@@ -473,6 +462,20 @@
     }
 
     /**
+     * @return The next package's device Id from its context.
+     * This device ID is used for permissions checking during attribution source validation.
+     *
+     * @hide
+     */
+    public int getNextDeviceId() {
+        if (mAttributionSourceState.next != null
+                && mAttributionSourceState.next.length > 0) {
+            return mAttributionSourceState.next[0].deviceId;
+        }
+        return Context.DEVICE_ID_DEFAULT;
+    }
+
+    /**
      * Checks whether this attribution source can be trusted. That is whether
      * the app it refers to created it and provided to the attribution chain.
      *
@@ -753,6 +756,9 @@
         @FlaggedApi(Flags.FLAG_SET_NEXT_ATTRIBUTION_SOURCE)
         public @NonNull Builder setNextAttributionSource(@NonNull AttributionSource value) {
             checkNotUsed();
+            if (value == null) {
+                throw new IllegalArgumentException("Null AttributionSource not permitted.");
+            }
             mBuilderFieldsSet |= 0x20;
             mAttributionSourceState.next =
                     new AttributionSourceState[]{value.mAttributionSourceState};
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 9e316a2..c8cae82 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4353,13 +4353,6 @@
             "android.intent.extra.BRIGHTNESS_DIALOG_IS_FULL_WIDTH";
 
     /**
-     * Activity Action: Shows the contrast setting dialog.
-     * @hide
-     */
-    public static final String ACTION_SHOW_CONTRAST_DIALOG =
-            "com.android.intent.action.SHOW_CONTRAST_DIALOG";
-
-    /**
      * Broadcast Action:  A global button was pressed.  Includes a single
      * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
      * caused the broadcast.
diff --git a/core/java/android/content/TEST_MAPPING b/core/java/android/content/TEST_MAPPING
index a2cfbf5..41a4288 100644
--- a/core/java/android/content/TEST_MAPPING
+++ b/core/java/android/content/TEST_MAPPING
@@ -56,6 +56,10 @@
         }
       ],
       "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java"]
+    },
+    {
+      "name": "CtsWindowManagerBackgroundActivityTestCases",
+      "file_patterns": ["(/|^)IntentSender.java"]
     }
   ],
   "ravenwood-presubmit": [
@@ -63,5 +67,7 @@
       "name": "CtsContentTestCasesRavenwood",
       "host": true
     }
+  ],
+  "postsubmit": [
   ]
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 83285e0..c506c97 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -270,11 +270,20 @@
     /**
      * Application level {@link android.content.pm.PackageManager.Property PackageManager
      * .Property} for a app to inform the installer that a file containing the app's android
-     * safety label data is bundled into the APK at the given path.
+     * safety label data is bundled into the APK as a raw resource.
+     *
+     * <p>For example:
+     * <pre>
+     * &lt;application&gt;
+     *   &lt;property
+     *     android:name="android.content.PROPERTY_ANDROID_SAFETY_LABEL"
+     *     android:resource="@raw/app-metadata"/&gt;
+     * &lt;/application&gt;
+     * </pre>
      * @hide
      */
-    public static final String PROPERTY_ANDROID_SAFETY_LABEL_PATH =
-            "android.content.SAFETY_LABEL_PATH";
+    public static final String PROPERTY_ANDROID_SAFETY_LABEL =
+            "android.content.PROPERTY_ANDROID_SAFETY_LABEL";
 
     /**
      * A property value set within the manifest.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 4b579e7..1f6730b 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2628,6 +2628,15 @@
             return Build.VERSION_CODES.CUR_DEVELOPMENT;
         }
 
+        // STOPSHIP: hack for the pre-release SDK
+        if (platformSdkCodenames.length == 0
+                && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
+                targetCode)) {
+            Slog.w(TAG, "Package requires development platform " + targetCode
+                    + ", returning current version " + Build.VERSION.SDK_INT);
+            return Build.VERSION.SDK_INT;
+        }
+
         // Otherwise, we're looking at an incompatible pre-release SDK.
         if (platformSdkCodenames.length > 0) {
             outError[0] = "Requires development platform " + targetCode
@@ -2699,6 +2708,15 @@
             return Build.VERSION_CODES.CUR_DEVELOPMENT;
         }
 
+        // STOPSHIP: hack for the pre-release SDK
+        if (platformSdkCodenames.length == 0
+                && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
+                minCode)) {
+            Slog.w(TAG, "Package requires min development platform " + minCode
+                    + ", returning current version " + Build.VERSION.SDK_INT);
+            return Build.VERSION.SDK_INT;
+        }
+
         // Otherwise, we're looking at an incompatible pre-release SDK.
         if (platformSdkCodenames.length > 0) {
             outError[0] = "Requires development platform " + minCode
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index be40143..cd3ce87 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -1492,12 +1492,12 @@
         /**
          * Sets which surfaces a shortcut will be excluded from.
          *
-         * If the shortcut is set to be excluded from {@link #SURFACE_LAUNCHER}, shortcuts will be
-         * excluded from the search result of {@link android.content.pm.LauncherApps#getShortcuts(
-         * android.content.pm.LauncherApps.ShortcutQuery, UserHandle)} nor
-         * {@link android.content.pm.ShortcutManager#getShortcuts(int)}. This generally means the
-         * shortcut would not be displayed by a launcher app (e.g. in Long-Press menu), while
-         * remain visible in other surfaces such as assistant or on-device-intelligence.
+         * This API is reserved for future extension. Currently, marking a shortcut to be
+         * excluded from {@link #SURFACE_LAUNCHER} will not publish the shortcut, thus
+         * the following operations will be a no-op:
+         * {@link android.content.pm.ShortcutManager#pushDynamicShortcut(android.content.pm.ShortcutInfo)},
+         * {@link android.content.pm.ShortcutManager#addDynamicShortcuts(List)}, and
+         * {@link android.content.pm.ShortcutManager#setDynamicShortcuts(List)}.
          */
         @NonNull
         public Builder setExcludedFromSurfaces(final int surfaces) {
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 45591d7..061e7f7 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -137,6 +137,18 @@
 }
 
 flag {
+    name: "get_package_storage_stats"
+    namespace: "system_performance"
+    is_exported: true
+    description: "Add dumpsys entry point for package StorageStats"
+    bug: "332905331"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "provide_info_of_apk_in_apex"
     is_exported: true
     namespace: "package_manager_service"
@@ -242,6 +254,14 @@
 }
 
 flag {
+    name: "wait_application_killed"
+    namespace: "package_manager_service"
+    description: "Feature flag to control whether to wait until the application is killed when clear application data"
+    bug: "31009094"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "component_state_changed_metrics"
     namespace: "package_manager_service"
     description: "Feature flag to log the metrics when the component state is changed."
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index cd1913b..e2a131c 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -240,3 +240,20 @@
     description: "Add entrypoint in Settings Reset options for deleting private space when lock is forgotten"
     bug: "329601751"
 }
+
+flag {
+    name: "allow_main_user_to_access_blocked_number_provider"
+    namespace: "multiuser"
+    description: "Allow MAIN user to access blocked number provider"
+    bug: "338579331"
+}
+
+flag {
+    name: "restrict_quiet_mode_credential_bug_fix_to_managed_profiles"
+    namespace: "profile_experiences"
+    description: "Use user states to check the state of quiet mode for managed profiles only"
+    bug: "332812630"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
index 153dd9a..c7403c0 100644
--- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
@@ -316,6 +316,15 @@
             return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
         }
 
+        // STOPSHIP: hack for the pre-release SDK
+        if (platformSdkCodenames.length == 0
+                && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
+                        minCode)) {
+            Slog.w(TAG, "Parsed package requires min development platform " + minCode
+                    + ", returning current version " + Build.VERSION.SDK_INT);
+            return input.success(Build.VERSION.SDK_INT);
+        }
+
         // Otherwise, we're looking at an incompatible pre-release SDK.
         if (platformSdkCodenames.length > 0) {
             return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
@@ -368,19 +377,27 @@
             return input.success(targetVers);
         }
 
+        // If it's a pre-release SDK and the codename matches this platform, it
+        // definitely targets this SDK.
+        if (matchTargetCode(platformSdkCodenames, targetCode)) {
+            return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
+        }
+
+        // STOPSHIP: hack for the pre-release SDK
+        if (platformSdkCodenames.length == 0
+                && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
+                        targetCode)) {
+            Slog.w(TAG, "Parsed package requires development platform " + targetCode
+                    + ", returning current version " + Build.VERSION.SDK_INT);
+            return input.success(Build.VERSION.SDK_INT);
+        }
+
         try {
             if (allowUnknownCodenames && UnboundedSdkLevel.isAtMost(targetCode)) {
                 return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
             }
         } catch (IllegalArgumentException e) {
-            // isAtMost() throws it when encountering an older SDK codename
-            return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, e.getMessage());
-        }
-
-        // If it's a pre-release SDK and the codename matches this platform, it
-        // definitely targets this SDK.
-        if (matchTargetCode(platformSdkCodenames, targetCode)) {
-            return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
+            return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, "Bad package SDK");
         }
 
         // Otherwise, we're looking at an incompatible pre-release SDK.
diff --git a/core/java/android/credentials/GetCandidateCredentialsResponse.java b/core/java/android/credentials/GetCandidateCredentialsResponse.java
index 3d8ccaa..c70eff4 100644
--- a/core/java/android/credentials/GetCandidateCredentialsResponse.java
+++ b/core/java/android/credentials/GetCandidateCredentialsResponse.java
@@ -18,6 +18,8 @@
 
 import android.annotation.Hide;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
 import android.content.Intent;
 import android.credentials.selection.GetCredentialProviderData;
 import android.os.Parcel;
@@ -39,6 +41,9 @@
     @NonNull
     private final List<GetCredentialProviderData> mCandidateProviderDataList;
 
+    @Nullable
+    private final ComponentName mPrimaryProviderComponentName;
+
     @NonNull
     private final Intent mIntent;
 
@@ -48,13 +53,15 @@
     @Hide
     public GetCandidateCredentialsResponse(
             @NonNull List<GetCredentialProviderData> candidateProviderDataList,
-            @NonNull Intent intent
+            @NonNull Intent intent,
+            @Nullable ComponentName primaryProviderComponentName
     ) {
         Preconditions.checkCollectionNotEmpty(
                 candidateProviderDataList,
                 /*valueName=*/ "candidateProviderDataList");
         mCandidateProviderDataList = new ArrayList<>(candidateProviderDataList);
         mIntent = intent;
+        mPrimaryProviderComponentName = primaryProviderComponentName;
     }
 
     /**
@@ -67,6 +74,16 @@
     }
 
     /**
+     * Returns the primary provider component name.
+     *
+     * @hide
+     */
+    @Nullable
+    public ComponentName getPrimaryProviderComponentName() {
+        return mPrimaryProviderComponentName;
+    }
+
+    /**
      * Returns candidate provider data list.
      *
      * @hide
@@ -83,12 +100,15 @@
 
         AnnotationValidations.validate(NonNull.class, null, mCandidateProviderDataList);
         mIntent = in.readTypedObject(Intent.CREATOR);
+
+        mPrimaryProviderComponentName = in.readTypedObject(ComponentName.CREATOR);
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeTypedList(mCandidateProviderDataList);
         dest.writeTypedObject(mIntent, flags);
+        dest.writeTypedObject(mPrimaryProviderComponentName, flags);
     }
 
     @Override
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index d683d72..1eb466c 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -731,6 +731,7 @@
      * commits, or is rolled back, either explicitly or by a call to
      * {@link #yieldIfContendedSafely}.
      */
+    // TODO(340874899) Provide an Executor overload
     public void beginTransactionWithListener(
             @Nullable SQLiteTransactionListener transactionListener) {
         beginTransaction(transactionListener, true);
@@ -760,6 +761,7 @@
      *            transaction begins, commits, or is rolled back, either
      *            explicitly or by a call to {@link #yieldIfContendedSafely}.
      */
+    // TODO(340874899) Provide an Executor overload
     public void beginTransactionWithListenerNonExclusive(
             @Nullable SQLiteTransactionListener transactionListener) {
         beginTransaction(transactionListener, false);
@@ -785,6 +787,8 @@
      *   }
      * </pre>
      */
+    // TODO(340874899) Provide an Executor overload
+    @SuppressLint("ExecutorRegistration")
     @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
     public void beginTransactionWithListenerReadOnly(
             @Nullable SQLiteTransactionListener transactionListener) {
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index a0e40f6..61f1ee1 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -34,6 +34,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.TestApi;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.graphics.Bitmap;
@@ -603,7 +604,6 @@
             mPromptInfo.setIsForLegacyFingerprintManager(sensorId);
             return this;
         }
-        // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java)
 
         /**
          * Set if emergency call button should show, for example if biometrics are
@@ -613,12 +613,33 @@
          * @hide
          */
         @NonNull
+        @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
         public Builder setShowEmergencyCallButton(boolean showEmergencyCallButton) {
             mPromptInfo.setShowEmergencyCallButton(showEmergencyCallButton);
             return this;
         }
 
         /**
+         * Set caller's component name for getting logo icon/description. This should only be used
+         * by ConfirmDeviceCredentialActivity, see b/337082634 for more context.
+         *
+         * @param componentNameForConfirmDeviceCredentialActivity set the component name for
+         *                                                        ConfirmDeviceCredentialActivity.
+         * @return This builder.
+         * @hide
+         */
+        @NonNull
+        @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
+        public Builder setComponentNameForConfirmDeviceCredentialActivity(
+                ComponentName componentNameForConfirmDeviceCredentialActivity) {
+            mPromptInfo.setComponentNameForConfirmDeviceCredentialActivity(
+                    componentNameForConfirmDeviceCredentialActivity);
+            return this;
+        }
+
+        // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java)
+
+        /**
          * Creates a {@link BiometricPrompt}.
          *
          * @return An instance of {@link BiometricPrompt}.
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 18b75c9..bb07b9b 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -19,6 +19,7 @@
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.ComponentName;
 import android.graphics.Bitmap;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -56,6 +57,7 @@
     private boolean mIsForLegacyFingerprintManager = false;
     private boolean mShowEmergencyCallButton = false;
     private boolean mUseParentProfileForDeviceCredential = false;
+    private ComponentName mComponentNameForConfirmDeviceCredentialActivity = null;
 
     public PromptInfo() {
 
@@ -87,6 +89,8 @@
         mIsForLegacyFingerprintManager = in.readBoolean();
         mShowEmergencyCallButton = in.readBoolean();
         mUseParentProfileForDeviceCredential = in.readBoolean();
+        mComponentNameForConfirmDeviceCredentialActivity = in.readParcelable(
+                ComponentName.class.getClassLoader(), ComponentName.class);
     }
 
     public static final Creator<PromptInfo> CREATOR = new Creator<PromptInfo>() {
@@ -132,10 +136,11 @@
         dest.writeBoolean(mIsForLegacyFingerprintManager);
         dest.writeBoolean(mShowEmergencyCallButton);
         dest.writeBoolean(mUseParentProfileForDeviceCredential);
+        dest.writeParcelable(mComponentNameForConfirmDeviceCredentialActivity, 0);
     }
 
     // LINT.IfChange
-    public boolean containsTestConfigurations() {
+    public boolean requiresTestOrInternalPermission() {
         if (mIsForLegacyFingerprintManager
                 && mAllowedSensorIds.size() == 1
                 && !mAllowBackgroundAuthentication) {
@@ -148,11 +153,15 @@
             return true;
         } else if (mIgnoreEnrollmentState) {
             return true;
+        } else if (mShowEmergencyCallButton) {
+            return true;
+        } else if (mComponentNameForConfirmDeviceCredentialActivity != null) {
+            return true;
         }
         return false;
     }
 
-    public boolean containsPrivateApiConfigurations() {
+    public boolean requiresInternalPermission() {
         if (mDisallowBiometricsIfPolicyExists) {
             return true;
         } else if (mUseDefaultTitle) {
@@ -177,7 +186,7 @@
      * Currently, logo res, logo bitmap, logo description, PromptContentViewWithMoreOptions needs
      * this permission.
      */
-    public boolean containsAdvancedApiConfigurations() {
+    public boolean requiresAdvancedPermission() {
         if (mLogoRes != -1) {
             return true;
         } else if (mLogoBitmap != null) {
@@ -305,6 +314,12 @@
         mShowEmergencyCallButton = showEmergencyCallButton;
     }
 
+    public void setComponentNameForConfirmDeviceCredentialActivity(
+            ComponentName componentNameForConfirmDeviceCredentialActivity) {
+        mComponentNameForConfirmDeviceCredentialActivity =
+                componentNameForConfirmDeviceCredentialActivity;
+    }
+
     public void setUseParentProfileForDeviceCredential(
             boolean useParentProfileForDeviceCredential) {
         mUseParentProfileForDeviceCredential = useParentProfileForDeviceCredential;
@@ -417,6 +432,10 @@
         return mShowEmergencyCallButton;
     }
 
+    public ComponentName getComponentNameForConfirmDeviceCredentialActivity() {
+        return mComponentNameForConfirmDeviceCredentialActivity;
+    }
+
     private void checkOnlyOneLogoSet() {
         if (mLogoRes != -1 && mLogoBitmap != null) {
             throw new IllegalStateException(
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index a019612..708f8a1 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -39,7 +39,6 @@
 import android.content.pm.PackageManager;
 import android.graphics.Point;
 import android.hardware.CameraExtensionSessionStats;
-import android.hardware.CameraIdRemapping;
 import android.hardware.CameraStatus;
 import android.hardware.ICameraService;
 import android.hardware.ICameraServiceListener;
@@ -838,7 +837,10 @@
         return new CameraExtensionCharacteristics(mContext, cameraId, characteristicsMap);
     }
 
-    private Map<String, CameraCharacteristics> getPhysicalIdToCharsMap(
+    /**
+     * @hide
+     */
+    public Map<String, CameraCharacteristics> getPhysicalIdToCharsMap(
             CameraCharacteristics chars) throws CameraAccessException {
         HashMap<String, CameraCharacteristics> physicalIdsToChars =
                 new HashMap<String, CameraCharacteristics>();
@@ -974,8 +976,6 @@
             final int oomScoreOffset, int rotationOverride) throws CameraAccessException {
         CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
         CameraDevice device = null;
-        Map<String, CameraCharacteristics> physicalIdsToChars =
-                getPhysicalIdToCharsMap(characteristics);
         synchronized (mLock) {
             ICameraDeviceUser cameraUser = null;
             CameraDevice.CameraDeviceSetup cameraDeviceSetup = null;
@@ -990,7 +990,7 @@
                         callback,
                         executor,
                         characteristics,
-                        physicalIdsToChars,
+                        this,
                         mContext.getApplicationInfo().targetSdkVersion,
                         mContext, cameraDeviceSetup);
             ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks();
@@ -1995,17 +1995,6 @@
     }
 
     /**
-     * Remaps Camera Ids in the CameraService.
-     *
-     * @hide
-     */
-    @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
-    public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping)
-            throws CameraAccessException, SecurityException, IllegalArgumentException {
-        CameraManagerGlobal.get().remapCameraIds(cameraIdRemapping);
-    }
-
-    /**
      * Injects session params into existing clients in the CameraService.
      *
      * @param cameraId       The camera id of client to inject session params into.
@@ -2116,13 +2105,6 @@
 
         private final Object mLock = new Object();
 
-        /**
-         * The active CameraIdRemapping. This will be used to refresh the cameraIdRemapping state
-         * in the CameraService every time we connect to it, including when the CameraService
-         * Binder dies and we reconnect to it.
-         */
-        @Nullable private CameraIdRemapping mActiveCameraIdRemapping;
-
         // Access only through getCameraService to deal with binder death
         private ICameraService mCameraService;
         private boolean mHasOpenCloseListenerPermission = false;
@@ -2274,41 +2256,6 @@
             } catch (RemoteException e) {
                 // Camera service died in all probability
             }
-
-            if (mActiveCameraIdRemapping != null) {
-                try {
-                    cameraService.remapCameraIds(mActiveCameraIdRemapping);
-                } catch (ServiceSpecificException e) {
-                    // Unexpected failure, ignore and continue.
-                    Log.e(TAG, "Unable to remap camera Ids in the camera service");
-                } catch (RemoteException e) {
-                    // Camera service died in all probability
-                }
-            }
-        }
-
-        /** Updates the cameraIdRemapping state in the CameraService. */
-        public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping)
-                throws CameraAccessException, SecurityException {
-            synchronized (mLock) {
-                ICameraService cameraService = getCameraService();
-                if (cameraService == null) {
-                    throw new CameraAccessException(
-                            CameraAccessException.CAMERA_DISCONNECTED,
-                            "Camera service is currently unavailable.");
-                }
-
-                try {
-                    cameraService.remapCameraIds(cameraIdRemapping);
-                    mActiveCameraIdRemapping = cameraIdRemapping;
-                } catch (ServiceSpecificException e) {
-                    throw ExceptionUtils.throwAsPublicException(e);
-                } catch (RemoteException e) {
-                    throw new CameraAccessException(
-                            CameraAccessException.CAMERA_DISCONNECTED,
-                            "Camera service is currently unavailable.");
-                }
-            }
         }
 
         /** Injects session params into an existing client for cameraid. */
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 7754e32..de26384 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -2359,7 +2359,10 @@
      * FPS.</p>
      * <p>If the session configuration is not supported, the AE mode reported in the
      * CaptureResult will be 'ON' instead of 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY'.</p>
-     * <p>The application can observe the CapturerResult field
+     * <p>When this AE mode is enabled, the CaptureResult field
+     * {@link CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE android.control.lowLightBoostState} will be present and not null. Otherwise, the
+     * {@link CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE android.control.lowLightBoostState} field will not be present in the CaptureResult.</p>
+     * <p>The application can observe the CaptureResult field
      * {@link CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE android.control.lowLightBoostState} to determine when low light boost is 'ACTIVE' or
      * 'INACTIVE'.</p>
      * <p>The low light boost is 'ACTIVE' once the scene lighting condition is less than the
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 5765a73..1460515 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2819,6 +2819,8 @@
      * <p>When low light boost is enabled by setting the AE mode to
      * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY', it can dynamically apply a low light
      * boost when the light level threshold is exceeded.</p>
+     * <p>This field is present in the CaptureResult when the AE mode is set to
+     * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY'. Otherwise, the field is not present.</p>
      * <p>This state indicates when low light boost is 'ACTIVE' and applied. Similarly, it can
      * indicate when it is not being applied by returning 'INACTIVE'.</p>
      * <p>This key will be absent from the CaptureResult if AE mode is not set to
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 6d9b51cb..2e1e90c 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -200,6 +200,8 @@
                 supportedCaptureSizes.put(format, supportedSizes);
             }
         }
+
+        int captureFormat = ImageFormat.UNKNOWN;
         Surface burstCaptureSurface = CameraExtensionUtils.getBurstCaptureSurface(
                 config.getOutputConfigurations(), supportedCaptureSizes);
         OutputConfiguration burstCaptureOutputConfig = null;
@@ -210,6 +212,12 @@
                 }
             }
             suitableSurfaceCount++;
+
+            if (Flags.analytics24q3()) {
+                CameraExtensionUtils.SurfaceInfo burstCaptureSurfaceInfo =
+                        CameraExtensionUtils.querySurface(burstCaptureSurface);
+                captureFormat = burstCaptureSurfaceInfo.mFormat;
+            }
         }
 
         if (suitableSurfaceCount != config.getOutputConfigurations().size()) {
@@ -249,6 +257,9 @@
                 burstCaptureOutputConfig, postviewOutputConfig, config.getStateCallback(),
                 config.getExecutor(), sessionId, token, config.getExtension());
 
+        if (Flags.analytics24q3()) {
+            ret.mStatsAggregator.setCaptureFormat(captureFormat);
+        }
         ret.mStatsAggregator.setClientName(ctx.getOpPackageName());
         ret.mStatsAggregator.setExtensionType(config.getExtension());
 
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 81bb9ac..e2b409f 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -31,6 +31,7 @@
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.hardware.camera2.CameraManager;
 import android.hardware.camera2.CameraMetadata;
 import android.hardware.camera2.CameraOfflineSession;
 import android.hardware.camera2.CaptureFailure;
@@ -146,7 +147,8 @@
 
     private final String mCameraId;
     private final CameraCharacteristics mCharacteristics;
-    private final Map<String, CameraCharacteristics> mPhysicalIdsToChars;
+    private Map<String, CameraCharacteristics> mPhysicalIdsToChars;
+    private final CameraManager mCameraManager;
     private final int mTotalPartialCount;
     private final Context mContext;
 
@@ -341,11 +343,12 @@
 
     public CameraDeviceImpl(String cameraId, StateCallback callback, Executor executor,
                         CameraCharacteristics characteristics,
-                        Map<String, CameraCharacteristics> physicalIdsToChars,
+                        @NonNull CameraManager manager,
                         int appTargetSdkVersion,
                         Context ctx,
                         @Nullable CameraDevice.CameraDeviceSetup cameraDeviceSetup) {
-        if (cameraId == null || callback == null || executor == null || characteristics == null) {
+        if (cameraId == null || callback == null || executor == null || characteristics == null
+                || manager == null) {
             throw new IllegalArgumentException("Null argument given");
         }
         mCameraId = cameraId;
@@ -357,7 +360,7 @@
             mDeviceExecutor = executor;
         }
         mCharacteristics = characteristics;
-        mPhysicalIdsToChars = physicalIdsToChars;
+        mCameraManager = manager;
         mAppTargetSdkVersion = appTargetSdkVersion;
         mContext = ctx;
         mCameraDeviceSetup = cameraDeviceSetup;
@@ -379,6 +382,18 @@
         }
     }
 
+    private Map<String, CameraCharacteristics> getPhysicalIdToChars() {
+        if (mPhysicalIdsToChars == null) {
+            try {
+                mPhysicalIdsToChars = mCameraManager.getPhysicalIdToCharsMap(mCharacteristics);
+            } catch (CameraAccessException e) {
+                Log.e(TAG, "Unable to query the physical characteristics map!");
+            }
+        }
+
+        return mPhysicalIdsToChars;
+    }
+
     public CameraDeviceCallbacks getCallbacks() {
         return mCallbacks;
     }
@@ -1598,7 +1613,7 @@
             return true;
         }
 
-        for (Map.Entry<String, CameraCharacteristics> entry : mPhysicalIdsToChars.entrySet()) {
+        for (Map.Entry<String, CameraCharacteristics> entry : getPhysicalIdToChars().entrySet()) {
             configMap = entry.getValue().get(ck);
 
             if (configMap != null &&
@@ -2621,7 +2636,7 @@
     public void createExtensionSession(ExtensionSessionConfiguration extensionConfiguration)
             throws CameraAccessException {
         HashMap<String, CameraCharacteristics> characteristicsMap = new HashMap<>(
-                mPhysicalIdsToChars);
+                getPhysicalIdToChars());
         characteristicsMap.put(mCameraId, mCharacteristics);
         boolean initializationFailed = true;
         IBinder token = new Binder(TAG + " : " + mNextSessionId++);
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 3ae3199..a4ae398 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -200,10 +200,18 @@
                 supportedCaptureSizes.put(format, supportedSizes);
             }
         }
+
+        int captureFormat = ImageFormat.UNKNOWN;
         Surface burstCaptureSurface = CameraExtensionUtils.getBurstCaptureSurface(
                 config.getOutputConfigurations(), supportedCaptureSizes);
         if (burstCaptureSurface != null) {
             suitableSurfaceCount++;
+
+            if (Flags.analytics24q3()) {
+                CameraExtensionUtils.SurfaceInfo burstCaptureSurfaceInfo =
+                        CameraExtensionUtils.querySurface(burstCaptureSurface);
+                captureFormat = burstCaptureSurfaceInfo.mFormat;
+            }
         }
 
         if (suitableSurfaceCount != config.getOutputConfigurations().size()) {
@@ -258,6 +266,9 @@
                 extensionChars.getAvailableCaptureResultKeys(config.getExtension()),
                 config.getExtension());
 
+        if (Flags.analytics24q3()) {
+            session.mStatsAggregator.setCaptureFormat(captureFormat);
+        }
         session.mStatsAggregator.setClientName(ctx.getOpPackageName());
         session.mStatsAggregator.setExtensionType(config.getExtension());
 
diff --git a/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java b/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java
index 3050a51..c75e418 100644
--- a/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java
+++ b/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java
@@ -70,6 +70,23 @@
     }
 
     /**
+     * Set the capture format.
+     *
+     * @param format Format of requested capture.
+     */
+    public void setCaptureFormat(int format) {
+        synchronized (mLock) {
+            if (mIsDone) {
+                return;
+            }
+            if (DEBUG) {
+                Log.v(TAG, "Setting capture format: " + format);
+            }
+            mStats.captureFormat = format;
+        }
+    }
+
+    /**
      * Set extension type.
      *
      * @param extensionType Type of extension. Must match one of
@@ -116,7 +133,8 @@
                 + "  cameraId: '" + stats.cameraId + "'\n"
                 + "  clientName: '" + stats.clientName + "'\n"
                 + "  type: '" + stats.type + "'\n"
-                + "  isAdvanced: '" + stats.isAdvanced + "'\n";
+                + "  isAdvanced: '" + stats.isAdvanced + "'\n"
+                + "  captureFormat: '" + stats.captureFormat + "'\n";
     }
 
     /**
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index d340f3f..3f2ef84 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -732,6 +732,10 @@
 
     /**
      * Get statically configured sensor properties.
+     * @deprecated Generally unsafe to use, use
+     * {@link FaceManager#addAuthenticatorsRegisteredCallback} API instead.
+     * In most cases this method will work as expected, but during early boot up, it will be
+     * null/empty and there is no way for the caller to know when it's actual value is ready.
      * @hide
      */
     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 25bfb2a..2ded615 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -1189,6 +1189,10 @@
 
     /**
      * Get statically configured sensor properties.
+     * @deprecated Generally unsafe to use, use
+     * {@link FingerprintManager#addAuthenticatorsRegisteredCallback} API instead.
+     * In most cases this method will work as expected, but during early boot up, it will be
+     * null/empty and there is no way for the caller to know when it's actual value is ready.
      * @hide
      */
     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index ac043d3..91b05c2 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -1353,9 +1353,6 @@
     /**
      * Get a snapshot of the real-time status of the devices on the CEC bus.
      *
-     * <p>This only applies to devices with switch functionality, which are devices with one
-     * or more than one HDMI inputs.
-     *
      * @return a list of {@link HdmiDeviceInfo} of the connected CEC devices on the CEC bus. An
      * empty list will be returned if there is none.
      */
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 243ae14..45b316a 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -148,8 +148,6 @@
 
     IInputDeviceBatteryState getBatteryState(int deviceId);
 
-    void setPointerIconType(int typeId);
-    void setCustomPointerIcon(in PointerIcon icon);
     boolean setPointerIcon(in PointerIcon icon, int displayId, int deviceId, int pointerId,
             in IBinder inputToken);
 
@@ -173,9 +171,9 @@
     void removeUniqueIdAssociationByDescriptor(in String inputDeviceDescriptor);
 
     // Add a runtime association between the input device and display, using device's port.
-    void addUniqueIdAssociation(in String inputPort, in String displayUniqueId);
+    void addUniqueIdAssociationByPort(in String inputPort, in String displayUniqueId);
     // Remove the runtime association between the input device and display, using device's port.
-    void removeUniqueIdAssociation(in String inputPort);
+    void removeUniqueIdAssociationByPort(in String inputPort);
 
     InputSensorInfo[] getSensorList(int deviceId);
 
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index dd4ea31..7527aa7 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -992,21 +992,14 @@
     }
 
     /**
-     * Changes the mouse pointer's icon shape into the specified id.
+     * This method exists for backwards-compatibility, and is a no-op.
      *
-     * @param iconId The id of the pointer graphic, as a value between
-     * {@link PointerIcon#TYPE_ARROW} and {@link PointerIcon#TYPE_HANDWRITING}.
-     *
+     * @deprecated
      * @hide
      */
     @UnsupportedAppUsage
     public void setPointerIconType(int iconId) {
-        mGlobal.setPointerIconType(iconId);
-    }
-
-    /** @hide */
-    public void setCustomPointerIcon(PointerIcon icon) {
-        mGlobal.setCustomPointerIcon(icon);
+        Log.e(TAG, "setPointerIcon: Unsupported app usage!");
     }
 
     /** @hide */
@@ -1101,10 +1094,9 @@
      */
     @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
     @TestApi
-    // TODO(b/324075859): Rename to addUniqueIdAssociationByPort
-    public void addUniqueIdAssociation(@NonNull String inputPort,
+    public void addUniqueIdAssociationByPort(@NonNull String inputPort,
             @NonNull String displayUniqueId) {
-        mGlobal.addUniqueIdAssociation(inputPort, displayUniqueId);
+        mGlobal.addUniqueIdAssociationByPort(inputPort, displayUniqueId);
     }
 
     /**
@@ -1117,9 +1109,8 @@
      */
     @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
     @TestApi
-    // TODO(b/324075859): Rename to removeUniqueIdAssociationByPort
-    public void removeUniqueIdAssociation(@NonNull String inputPort) {
-        mGlobal.removeUniqueIdAssociation(inputPort);
+    public void removeUniqueIdAssociationByPort(@NonNull String inputPort) {
+        mGlobal.removeUniqueIdAssociationByPort(inputPort);
     }
 
     /**
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index a9c97b1..fcd5a3e 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -1411,28 +1411,6 @@
     }
 
     /**
-     * @see InputManager#setPointerIconType(int)
-     */
-    public void setPointerIconType(int iconId) {
-        try {
-            mIm.setPointerIconType(iconId);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * @see InputManager#setCustomPointerIcon(PointerIcon)
-     */
-    public void setCustomPointerIcon(PointerIcon icon) {
-        try {
-            mIm.setCustomPointerIcon(icon);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * @see InputManager#setPointerIcon(PointerIcon, int, int, int, IBinder)
      */
     public boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId, int pointerId,
@@ -1467,22 +1445,23 @@
     }
 
     /**
-     * @see InputManager#addUniqueIdAssociation(String, String)
+     * @see InputManager#addUniqueIdAssociationByPort(String, String)
      */
-    public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) {
+    public void addUniqueIdAssociationByPort(@NonNull String inputPort,
+            @NonNull String displayUniqueId) {
         try {
-            mIm.addUniqueIdAssociation(inputPort, displayUniqueId);
+            mIm.addUniqueIdAssociationByPort(inputPort, displayUniqueId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     /**
-     * @see InputManager#removeUniqueIdAssociation(String)
+     * @see InputManager#removeUniqueIdAssociationByPort(String)
      */
-    public void removeUniqueIdAssociation(@NonNull String inputPort) {
+    public void removeUniqueIdAssociationByPort(@NonNull String inputPort) {
         try {
-            mIm.removeUniqueIdAssociation(inputPort);
+            mIm.removeUniqueIdAssociationByPort(inputPort);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/hardware/usb/DeviceFilter.java b/core/java/android/hardware/usb/DeviceFilter.java
index 66b0a42..3a271b4 100644
--- a/core/java/android/hardware/usb/DeviceFilter.java
+++ b/core/java/android/hardware/usb/DeviceFilter.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.usb.flags.Flags;
 import android.service.usb.UsbDeviceFilterProto;
 import android.util.Slog;
 
@@ -57,9 +58,12 @@
     public final String mProductName;
     // USB device serial number string (or null for unspecified)
     public final String mSerialNumber;
+    // USB interface name (or null for unspecified). This will be used when matching devices using
+    // the available interfaces.
+    public final String mInterfaceName;
 
     public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol,
-            String manufacturer, String product, String serialnum) {
+            String manufacturer, String product, String serialnum, String interfaceName) {
         mVendorId = vid;
         mProductId = pid;
         mClass = clasz;
@@ -68,6 +72,7 @@
         mManufacturerName = manufacturer;
         mProductName = product;
         mSerialNumber = serialnum;
+        mInterfaceName = interfaceName;
     }
 
     public DeviceFilter(UsbDevice device) {
@@ -79,6 +84,7 @@
         mManufacturerName = device.getManufacturerName();
         mProductName = device.getProductName();
         mSerialNumber = device.getSerialNumber();
+        mInterfaceName = null;
     }
 
     public DeviceFilter(@NonNull DeviceFilter filter) {
@@ -90,6 +96,7 @@
         mManufacturerName = filter.mManufacturerName;
         mProductName = filter.mProductName;
         mSerialNumber = filter.mSerialNumber;
+        mInterfaceName = filter.mInterfaceName;
     }
 
     public static DeviceFilter read(XmlPullParser parser)
@@ -102,7 +109,7 @@
         String manufacturerName = null;
         String productName = null;
         String serialNumber = null;
-
+        String interfaceName = null;
         int count = parser.getAttributeCount();
         for (int i = 0; i < count; i++) {
             String name = parser.getAttributeName(i);
@@ -114,6 +121,8 @@
                 productName = value;
             } else if ("serial-number".equals(name)) {
                 serialNumber = value;
+            }  else if ("interface-name".equals(name)) {
+                interfaceName = value;
             } else {
                 int intValue;
                 int radix = 10;
@@ -144,7 +153,7 @@
         }
         return new DeviceFilter(vendorId, productId,
                 deviceClass, deviceSubclass, deviceProtocol,
-                manufacturerName, productName, serialNumber);
+                manufacturerName, productName, serialNumber, interfaceName);
     }
 
     public void write(XmlSerializer serializer) throws IOException {
@@ -173,13 +182,25 @@
         if (mSerialNumber != null) {
             serializer.attribute(null, "serial-number", mSerialNumber);
         }
+        if (mInterfaceName != null) {
+            serializer.attribute(null, "interface-name", mInterfaceName);
+        }
         serializer.endTag(null, "usb-device");
     }
 
-    private boolean matches(int clasz, int subclass, int protocol) {
-        return ((mClass == -1 || clasz == mClass) &&
-                (mSubclass == -1 || subclass == mSubclass) &&
-                (mProtocol == -1 || protocol == mProtocol));
+    private boolean matches(int usbClass, int subclass, int protocol) {
+        return ((mClass == -1 || usbClass == mClass)
+                && (mSubclass == -1 || subclass == mSubclass)
+                && (mProtocol == -1 || protocol == mProtocol));
+    }
+
+    private boolean matches(int usbClass, int subclass, int protocol, String interfaceName) {
+        if (Flags.enableInterfaceNameDeviceFilter()) {
+            return matches(usbClass, subclass, protocol)
+                    && (mInterfaceName == null || mInterfaceName.equals(interfaceName));
+        } else {
+            return matches(usbClass, subclass, protocol);
+        }
     }
 
     public boolean matches(UsbDevice device) {
@@ -204,7 +225,7 @@
         for (int i = 0; i < count; i++) {
             UsbInterface intf = device.getInterface(i);
             if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(),
-                    intf.getInterfaceProtocol())) return true;
+                    intf.getInterfaceProtocol(), intf.getName())) return true;
         }
 
         return false;
@@ -320,11 +341,12 @@
 
     @Override
     public String toString() {
-        return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId +
-                ",mClass=" + mClass + ",mSubclass=" + mSubclass +
-                ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName +
-                ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber +
-                "]";
+        return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId
+                + ",mClass=" + mClass + ",mSubclass=" + mSubclass
+                + ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName
+                + ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber
+                + ",mInterfaceName=" + mInterfaceName
+                + "]";
     }
 
     /**
diff --git a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
index 94df160..40e5ffb 100644
--- a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
@@ -16,3 +16,11 @@
     description: "Feature flag for the api to check if a port supports mode change"
     bug: "323470419"
 }
+
+flag {
+    name: "enable_interface_name_device_filter"
+    is_exported: true
+    namespace: "usb"
+    description: "Feature flag to enable interface name as a parameter for device filter"
+    bug: "312828160"
+}
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 594ec18..334b231 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -173,6 +173,12 @@
     public static final String FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY = "low_power_standby";
     /** @hide */
     public static final String FIREWALL_CHAIN_NAME_BACKGROUND = "background";
+    /** @hide */
+    public static final String FIREWALL_CHAIN_NAME_METERED_ALLOW = "metered_allow";
+    /** @hide */
+    public static final String FIREWALL_CHAIN_NAME_METERED_DENY_USER = "metered_deny_user";
+    /** @hide */
+    public static final String FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN = "metered_deny_admin";
 
     private static final boolean ALLOW_PLATFORM_APP_POLICY = true;
 
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 05a3e18..fedc97d 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -1387,7 +1387,11 @@
          * @param scheme name or {@code null} if this is a relative Uri
          */
         public Builder scheme(String scheme) {
-            this.scheme = scheme;
+            if (scheme != null) {
+                this.scheme = scheme.replaceAll("://", "");
+            } else {
+                this.scheme = null;
+            }
             return this;
         }
 
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index fea2c25..d4d1ed2 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -38,6 +38,16 @@
 }
 
 flag{
+    name: "enforce_main_user"
+    namespace: "vcn"
+    description: "Enforce main user to make VCN HSUM compatible"
+    bug: "310310661"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag{
     name: "handle_seq_num_leap"
     namespace: "vcn"
     description: "Do not report bad network when there is a suspected sequence number leap"
@@ -45,4 +55,14 @@
     metadata {
       purpose: PURPOSE_BUGFIX
     }
+}
+
+flag{
+    name: "allow_disable_ipsec_loss_detector"
+    namespace: "vcn"
+    description: "Allow disabling IPsec packet loss detector"
+    bug: "336638836"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
 }
\ No newline at end of file
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 9a63394..49ab15a 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -429,10 +429,9 @@
                         "Lazy values ref count below 0");
                 // No more lazy values in mMap, so we can recycle the parcel early rather than
                 // waiting for the next GC run
-                if (mLazyValues == 0) {
-                    Preconditions.checkState(mWeakParcelledData.get() != null,
-                            "Parcel recycled earlier than expected");
-                    recycleParcel(mWeakParcelledData.get());
+                Parcel parcel = mWeakParcelledData.get();
+                if (mLazyValues == 0 && parcel != null) {
+                    recycleParcel(parcel);
                     mWeakParcelledData = null;
                 }
             }
diff --git a/core/java/android/os/IHintManager.aidl b/core/java/android/os/IHintManager.aidl
index e057a85..360b2ac 100644
--- a/core/java/android/os/IHintManager.aidl
+++ b/core/java/android/os/IHintManager.aidl
@@ -18,6 +18,7 @@
 package android.os;
 
 import android.os.IHintSession;
+import android.hardware.power.ChannelConfig;
 import android.hardware.power.SessionConfig;
 import android.hardware.power.SessionTag;
 
@@ -27,6 +28,9 @@
      * Creates a {@link Session} for the given set of threads and associates to a binder token.
      * Returns a config if creation is not supported, and HMS had to use the
      * legacy creation method.
+     *
+     * Throws UnsupportedOperationException if ADPF is not supported, and IllegalStateException
+     * if creation is supported but fails.
      */
     IHintSession createHintSessionWithConfig(in IBinder token, in int[] threadIds,
             in long durationNanos, in SessionTag tag, out @nullable SessionConfig config);
@@ -38,4 +42,12 @@
 
     void setHintSessionThreads(in IHintSession hintSession, in int[] tids);
     int[] getHintSessionThreadIds(in IHintSession hintSession);
+
+    /**
+     * Returns FMQ channel information for the caller, which it associates to a binder token.
+     *
+     * Throws IllegalStateException if FMQ channel creation fails.
+     */
+    ChannelConfig getSessionChannel(in IBinder token);
+    oneway void closeSessionChannel();
 }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index c6a9203..2f0d634 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1930,12 +1930,10 @@
     public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
 
     /**
-     * This user restriction specifies if the user is able to add SIMs to the device.
+     * This user restriction specifies if the user is able to add embedded SIMs to the device.
      *
      * <p>
-     * This restriction blocks the download of embedded SIMs, and disables any physical SIMs.
-     * If any embedded SIMs are already on the device, then they are removed. This restriction
-     * does not affect SIMs provisioned to the device by device owners or profile owners.
+     * This restriction blocks the download of embedded SIMs.
      *
      * <p>
      * This restriction can only be set by a device owner or a profile owner of an
@@ -1951,6 +1949,7 @@
      *
      * <p>Key for user restrictions.
      * <p>Type: Boolean
+     *
      * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
      * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
diff --git a/core/java/android/os/VintfObject.java b/core/java/android/os/VintfObject.java
index 5056557..bb89e07 100644
--- a/core/java/android/os/VintfObject.java
+++ b/core/java/android/os/VintfObject.java
@@ -17,8 +17,11 @@
 package android.os;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.TestApi;
+import android.app.ActivityThread;
 
+import java.io.IOException;
 import java.util.Map;
 
 /**
@@ -113,5 +116,20 @@
     @TestApi
     public static native Long getTargetFrameworkCompatibilityMatrixVersion();
 
+    /**
+     * Executes a shell command using shell user identity, and return the standard output in string.
+     *
+     * @hide
+     */
+    private static @Nullable String runShellCommand(@NonNull String command) throws IOException {
+        var activityThread = ActivityThread.currentActivityThread();
+        var instrumentation = activityThread.getInstrumentation();
+        var automation = instrumentation.getUiAutomation();
+        var pfd = automation.executeShellCommand(command);
+        try (var is = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
+            return new String(is.readAllBytes());
+        }
+    }
+
     private VintfObject() {}
 }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index d45a17f..91ad22f 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -120,6 +120,8 @@
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * StorageManager is the interface to the systems storage service. The storage
@@ -2512,6 +2514,9 @@
         return userId * PER_USER_RANGE + projectId;
     }
 
+    private static final Pattern PATTERN_USER_ID = Pattern.compile(
+            "(?i)^/storage/emulated/([0-9]+)");
+
     /**
      * Let StorageManager know that the quota type for a file on external storage should
      * be updated. Android tracks quotas for various media types. Consequently, this should be
@@ -2541,26 +2546,35 @@
     @SystemApi
     public void updateExternalStorageFileQuotaType(@NonNull File path,
             @QuotaType int quotaType) throws IOException {
+        if (!path.exists()) return;
+
         long projectId;
         final String filePath = path.getCanonicalPath();
-        int volFlags = FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE;
-        // If caller has MANAGE_EXTERNAL_STORAGE permission, results from User Profile(s) are also
-        // returned by enabling FLAG_INCLUDE_SHARED_PROFILE.
-        if (mContext.checkSelfPermission(MANAGE_EXTERNAL_STORAGE) == PERMISSION_GRANTED) {
-            volFlags |= FLAG_INCLUDE_SHARED_PROFILE;
-        }
-        final StorageVolume[] availableVolumes = getVolumeList(mContext.getUserId(), volFlags);
-        final StorageVolume volume = getStorageVolume(availableVolumes, path);
-        if (volume == null) {
-            Log.w(TAG, "Failed to update quota type for " + filePath);
-            return;
-        }
-        if (!volume.isEmulated()) {
-            // We only support quota tracking on emulated filesystems
-            return;
+
+        final int userId;
+        final Matcher matcher = PATTERN_USER_ID.matcher(filePath);
+        if (matcher.find()) {
+            userId = Integer.parseInt(matcher.group(1));
+        } else {  // fallback
+            int volFlags = FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE;
+            // If caller has MANAGE_EXTERNAL_STORAGE permission, results from User Profile(s) are
+            // also returned by enabling FLAG_INCLUDE_SHARED_PROFILE.
+            if (mContext.checkSelfPermission(MANAGE_EXTERNAL_STORAGE) == PERMISSION_GRANTED) {
+                volFlags |= FLAG_INCLUDE_SHARED_PROFILE;
+            }
+            final StorageVolume[] availableVolumes = getVolumeList(mContext.getUserId(), volFlags);
+            final StorageVolume volume = getStorageVolume(availableVolumes, path);
+            if (volume == null) {
+                Log.w(TAG, "Failed to update quota type for " + filePath);
+                return;
+            }
+            if (!volume.isEmulated()) {
+                // We only support quota tracking on emulated filesystems
+                return;
+            }
+            userId = volume.getOwner().getIdentifier();
         }
 
-        final int userId = volume.getOwner().getIdentifier();
         if (userId < 0) {
             throw new IllegalStateException("Failed to update quota type for " + filePath);
         }
diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS
index d2f4b50..857bacd 100644
--- a/core/java/android/permission/OWNERS
+++ b/core/java/android/permission/OWNERS
@@ -1,14 +1,13 @@
 # Bug component: 137825
 
-augale@google.com
 evanseverson@google.com
 fayey@google.com
 jaysullivan@google.com
 joecastro@google.com
-kvakil@google.com
 mrulhania@google.com
 ntmyren@google.com
 rmacgregor@google.com
 theianchen@google.com
 yutingfang@google.com
 zhanghai@google.com
+kiranmr@google.com
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index b588308..2ca58d1 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -44,14 +44,6 @@
 }
 
 flag {
-  name: "attribution_source_constructor"
-  is_exported: true
-  namespace: "permissions"
-  description: "enable AttributionSource(int, int, String, String, IBinder, String[], AttributionSource)"
-  bug: "304478648"
-}
-
-flag {
     name: "enhanced_confirmation_mode_apis_enabled"
     is_exported: true
     is_fixed_read_only: true
@@ -174,6 +166,16 @@
 }
 
 flag {
+    name: "finish_running_ops_for_killed_packages"
+    namespace: "permissions"
+    description: "Finish all appops for a dead app process"
+    bug: "234630570"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "runtime_permission_appops_mapping_enabled"
     is_fixed_read_only: true
     namespace: "permissions"
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 120846c..708c196 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -2017,7 +2017,7 @@
                 return false;
             }
             final UserInfo userInfo = userManager.getUserInfo(userId);
-            return userInfo != null && !userInfo.isManagedProfile();
+            return userInfo != null && !userInfo.isProfile();
         }
 
         /**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e6ddf35..4f5b67c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5123,13 +5123,6 @@
         public static final String SCREEN_BRIGHTNESS = "screen_brightness";
 
         /**
-         * The screen backlight brightness between 0.0f and 1.0f.
-         * @hide
-         */
-        @Readable
-        public static final String SCREEN_BRIGHTNESS_FLOAT = "screen_brightness_float";
-
-        /**
          * Control whether to enable automatic brightness mode.
          */
         @Readable
@@ -6273,7 +6266,6 @@
             PUBLIC_SETTINGS.add(DIM_SCREEN);
             PUBLIC_SETTINGS.add(SCREEN_OFF_TIMEOUT);
             PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS);
-            PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_FLOAT);
             PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_MODE);
             PUBLIC_SETTINGS.add(MODE_RINGER_STREAMS_AFFECTED);
             PUBLIC_SETTINGS.add(MUTE_STREAMS_AFFECTED);
@@ -11181,6 +11173,35 @@
                 "visual_query_accessibility_detection_enabled";
 
         /**
+         * Timeout to be used for unbinding to the configured remote
+         * {@link android.service.ondeviceintelligence.OnDeviceIntelligenceService} if there are no
+         * requests in the queue. A value of -1 represents to never unbind.
+         *
+         * @hide
+         */
+        public static final String ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS =
+                "on_device_intelligence_unbind_timeout_ms";
+
+
+        /**
+         * Timeout that represents maximum idle time before which a callback should be populated.
+         *
+         * @hide
+         */
+        public static final String ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS =
+                "on_device_intelligence_idle_timeout_ms";
+
+        /**
+         * Timeout to be used for unbinding to the configured remote
+         * {@link android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService} if there
+         * are no requests in the queue. A value of -1 represents to never unbind.
+         *
+         * @hide
+         */
+        public static final String ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS =
+                "on_device_inference_unbind_timeout_ms";
+
+        /**
          * Control whether Night display is currently activated.
          * @hide
          */
@@ -14917,6 +14938,17 @@
         public static final String DROPBOX_TAG_PREFIX = "dropbox:";
 
         /**
+         * Lines of kernel logs to include with system crash/ANR/etc. reports, as a
+         * prefix of the dropbox tag of the report type. For example,
+         * "kernel_logs_for_system_server_anr" controls the lines of kernel logs
+         * captured with system server ANR reports. 0 to disable.
+         *
+         * @hide
+         */
+        @Readable
+        public static final String ERROR_KERNEL_LOG_PREFIX = "kernel_logs_for_";
+
+        /**
          * Lines of logcat to include with system crash/ANR/etc. reports, as a
          * prefix of the dropbox tag of the report type. For example,
          * "logcat_for_system_server_anr" controls the lines of logcat captured
diff --git a/core/java/android/provider/TEST_MAPPING b/core/java/android/provider/TEST_MAPPING
index d5ac7a7..2eb285d 100644
--- a/core/java/android/provider/TEST_MAPPING
+++ b/core/java/android/provider/TEST_MAPPING
@@ -8,6 +8,9 @@
                 }
             ]
         },
+	{
+	    "name": "CtsMediaProviderTestCases"
+	},
         {
             "name": "CalendarProviderTests"
         },
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 51758aa..ee5e533 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -81,8 +81,15 @@
 }
 
 flag {
-  name: "report_primary_auth_attempts"
-  namespace: "biometrics"
-  description: "Report primary auth attempts from LockSettingsService"
-  bug: "285053096"
+    name: "report_primary_auth_attempts"
+    namespace: "biometrics"
+    description: "Report primary auth attempts from LockSettingsService"
+    bug: "285053096"
+}
+
+flag {
+    name: "dump_attestation_verifications"
+    namespace: "hardware_backed_security"
+    description: "Add a dump capability for attestation_verification service"
+    bug: "335498868"
 }
diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index 92f2c32..3cd705a 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -86,7 +86,8 @@
             @NonNull Context context,
             @NonNull ComponentName serviceComponent,
             int userId,
-            boolean isSystemProvider)
+            boolean isSystemProvider,
+            boolean isPrimary)
             throws PackageManager.NameNotFoundException {
         return create(
                 context,
@@ -94,7 +95,7 @@
                 isSystemProvider,
                 /* disableSystemAppVerificationForTests= */ false,
                 /* isEnabled= */ false,
-                /* isPrimary= */ false);
+                isPrimary);
     }
 
     /**
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 5f6bdbf..38ab590 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -18,7 +18,7 @@
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.service.dreams.Flags.dreamHandlesConfirmKeys;
-import static android.service.dreams.Flags.dreamTracksFocus;
+import static android.service.dreams.Flags.dreamHandlesBeingObscured;
 
 import android.annotation.FlaggedApi;
 import android.annotation.IdRes;
@@ -571,15 +571,6 @@
     /** {@inheritDoc} */
     @Override
     public void onWindowFocusChanged(boolean hasFocus) {
-        if (!dreamTracksFocus()) {
-            return;
-        }
-
-        try {
-            mDreamManager.onDreamFocusChanged(hasFocus);
-        } catch (RemoteException ex) {
-            // system server died
-        }
     }
 
     /** {@inheritDoc} */
@@ -1737,7 +1728,7 @@
 
         @Override
         public void comeToFront() {
-            if (!dreamTracksFocus()) {
+            if (!dreamHandlesBeingObscured()) {
                 return;
             }
 
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index 85f0368..cf98bfe0 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -48,5 +48,6 @@
     void setSystemDreamComponent(in ComponentName componentName);
     void registerDreamOverlayService(in ComponentName componentName);
     void startDreamActivity(in Intent intent);
-    void onDreamFocusChanged(in boolean hasFocus);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)")
+    oneway void setDreamIsObscured(in boolean isObscured);
 }
diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig
index a42eaff..54d950c 100644
--- a/core/java/android/service/dreams/flags.aconfig
+++ b/core/java/android/service/dreams/flags.aconfig
@@ -39,8 +39,11 @@
 }
 
 flag {
-  name: "dream_tracks_focus"
+  name: "dream_handles_being_obscured"
   namespace: "communal"
-  description: "This flag enables the ability for dreams to track whether or not they have focus"
-  bug: "331798001"
+  description: "This flag enables the ability for dreams to handle being obscured"
+  bug: "337302237"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
 }
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index 76889df..88da8eb 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -37,9 +38,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
-
 import com.android.internal.os.SomeArgs;
-
 import java.lang.annotation.Retention;
 import java.util.List;
 
@@ -116,6 +115,7 @@
      */
     protected Handler mHandler;
 
+    @SuppressLint("OnNameExpected")
     @Override
     protected void attachBaseContext(Context base) {
         super.attachBaseContext(base);
diff --git a/core/java/android/service/notification/NotificationStats.java b/core/java/android/service/notification/NotificationStats.java
index 07367df..caa0a9c 100644
--- a/core/java/android/service/notification/NotificationStats.java
+++ b/core/java/android/service/notification/NotificationStats.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.app.Flags;
 import android.app.RemoteInput;
@@ -229,6 +230,7 @@
     /**
      * Records that the user has replied to a notification that has a smart reply at least once.
      */
+    @SuppressLint("GetterSetterNames")
     @FlaggedApi(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
     public void setSmartReplied() {
         mSmartReplied = true;
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index 1d7091c..910c462 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -1007,6 +1007,7 @@
         /**
          * Set whether priority channels are permitted to break through DND.
          */
+        @SuppressLint("BuilderSetStyle")
         @FlaggedApi(Flags.FLAG_MODES_API)
         public @NonNull Builder allowPriorityChannels(boolean allow) {
             mZenPolicy.mAllowChannels = allow ? CHANNEL_POLICY_PRIORITY : CHANNEL_POLICY_NONE;
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
index 29a6db6..144c1cd 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@@ -105,6 +105,26 @@
     public static final String SERVICE_INTERFACE =
             "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
 
+    // TODO(339594686): make API
+    /**
+     * @hide
+     */
+    public static final String REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY =
+            "register_model_update_callback";
+    /**
+     * @hide
+     */
+    public static final String MODEL_LOADED_BUNDLE_KEY = "model_loaded";
+    /**
+     * @hide
+     */
+    public static final String MODEL_UNLOADED_BUNDLE_KEY = "model_unloaded";
+
+    /**
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_UPDATE_BUNDLE_KEY = "device_config_update";
+
     private IRemoteStorageService mRemoteStorageService;
 
     /**
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index d174bef..9589785 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1025,7 +1025,8 @@
             mWallpaperDimAmount = (!mShouldDimByDefault) ? mCustomDimAmount
                     : Math.max(mDefaultDimAmount, mCustomDimAmount);
 
-            if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null
+            if (!ENABLE_WALLPAPER_DIMMING
+                    || mBbqSurfaceControl == null || !mBbqSurfaceControl.isValid()
                     || mWallpaperDimAmount == mPreviousWallpaperDimAmount) {
                 return;
             }
diff --git a/core/java/android/tracing/inputmethod/InputMethodDataSource.java b/core/java/android/tracing/inputmethod/InputMethodDataSource.java
new file mode 100644
index 0000000..5c5ad69
--- /dev/null
+++ b/core/java/android/tracing/inputmethod/InputMethodDataSource.java
@@ -0,0 +1,58 @@
+/*
+ * 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.tracing.inputmethod;
+
+import android.annotation.NonNull;
+import android.tracing.perfetto.DataSource;
+import android.tracing.perfetto.DataSourceInstance;
+import android.tracing.perfetto.StartCallbackArguments;
+import android.tracing.perfetto.StopCallbackArguments;
+import android.util.proto.ProtoInputStream;
+
+/**
+ * @hide
+ */
+public final class InputMethodDataSource
+        extends DataSource<DataSourceInstance, Void, Void> {
+    public static final String DATA_SOURCE_NAME = "android.inputmethod";
+
+    @NonNull
+    private final Runnable mOnStartCallback;
+    @NonNull
+    private final Runnable mOnStopCallback;
+
+    public InputMethodDataSource(@NonNull Runnable onStart, @NonNull Runnable onStop) {
+        super(DATA_SOURCE_NAME);
+        mOnStartCallback = onStart;
+        mOnStopCallback = onStop;
+    }
+
+    @Override
+    public DataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) {
+        return new DataSourceInstance(this, instanceIndex) {
+            @Override
+            protected void onStart(StartCallbackArguments args) {
+                mOnStartCallback.run();
+            }
+
+            @Override
+            protected void onStop(StopCallbackArguments args) {
+                mOnStopCallback.run();
+            }
+        };
+    }
+}
diff --git a/core/java/android/tracing/perfetto/DataSource.java b/core/java/android/tracing/perfetto/DataSource.java
index b9ab82c..4de7b62 100644
--- a/core/java/android/tracing/perfetto/DataSource.java
+++ b/core/java/android/tracing/perfetto/DataSource.java
@@ -85,7 +85,7 @@
                         new TracingContext<>(this, instanceIndex);
                 fun.trace(ctx);
 
-                ctx.flush();
+                nativeWritePackets(mNativeObj, ctx.getAndClearAllPendingTracePackets());
             } while (nativePerfettoDsTraceIterateNext(mNativeObj));
         } finally {
             nativePerfettoDsTraceIterateBreak(mNativeObj);
@@ -130,7 +130,8 @@
      * @param params Params to initialize the datasource with.
      */
     public void register(DataSourceParams params) {
-        nativeRegisterDataSource(this.mNativeObj, params.bufferExhaustedPolicy);
+        nativeRegisterDataSource(this.mNativeObj, params.bufferExhaustedPolicy,
+                params.willNotifyOnStop, params.noFlush);
     }
 
     /**
@@ -163,8 +164,8 @@
         return this.createInstance(inputStream, instanceIndex);
     }
 
-    private static native void nativeRegisterDataSource(
-            long dataSourcePtr, int bufferExhaustedPolicy);
+    private static native void nativeRegisterDataSource(long dataSourcePtr,
+            int bufferExhaustedPolicy, boolean willNotifyOnStop, boolean noFlush);
 
     private static native long nativeCreate(DataSource thiz, String name);
     private static native void nativeFlushAll(long nativeDataSourcePointer);
@@ -179,4 +180,6 @@
     private static native boolean nativePerfettoDsTraceIterateNext(long dataSourcePtr);
     private static native void nativePerfettoDsTraceIterateBreak(long dataSourcePtr);
     private static native int nativeGetPerfettoDsInstanceIndex(long dataSourcePtr);
+
+    private static native void nativeWritePackets(long dataSourcePtr, byte[][] packetData);
 }
diff --git a/core/java/android/tracing/perfetto/DataSourceParams.java b/core/java/android/tracing/perfetto/DataSourceParams.java
index 6cd04e3..e50f9d7 100644
--- a/core/java/android/tracing/perfetto/DataSourceParams.java
+++ b/core/java/android/tracing/perfetto/DataSourceParams.java
@@ -46,12 +46,67 @@
     // after a while.
     public static final int PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT = 1;
 
-    public static DataSourceParams DEFAULTS =
-            new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP);
+    public static DataSourceParams DEFAULTS = new DataSourceParams.Builder().build();
 
-    public DataSourceParams(@PerfettoDsBufferExhausted int bufferExhaustedPolicy) {
+    private DataSourceParams(@PerfettoDsBufferExhausted int bufferExhaustedPolicy,
+            boolean willNotifyOnStop, boolean noFlush) {
         this.bufferExhaustedPolicy = bufferExhaustedPolicy;
+        this.willNotifyOnStop = willNotifyOnStop;
+        this.noFlush = noFlush;
     }
 
     public final @PerfettoDsBufferExhausted int bufferExhaustedPolicy;
+    public final boolean willNotifyOnStop;
+    public final boolean noFlush;
+
+    /**
+     * DataSource Parameters builder
+     *
+     * @hide
+     */
+    public static final class Builder {
+        /**
+         * Specify behavior when running out of shared memory buffer space.
+         */
+        public Builder setBufferExhaustedPolicy(@PerfettoDsBufferExhausted int value) {
+            this.mBufferExhaustedPolicy = value;
+            return this;
+        }
+
+        /**
+         * If true, the data source is expected to ack the stop request through the
+         * NotifyDataSourceStopped() IPC. If false, the service won't wait for an ack.
+         * Set this parameter to false when dealing with potentially frozen producers
+         * that wouldn't be able to quickly ack the stop request.
+         *
+         * Default value: true
+         */
+        public Builder setWillNotifyOnStop(boolean value) {
+            this.mWillNotifyOnStop = value;
+            return this;
+        }
+
+        /**
+         * If true, the service won't emit flush requests for this data source. This
+         * allows the service to reduce the flush-related IPC traffic and better deal
+         * with frozen producers (see go/perfetto-frozen).
+         */
+        public Builder setNoFlush(boolean value) {
+            this.mNoFlush = value;
+            return this;
+        }
+
+        /**
+         * Build the DataSource parameters.
+         */
+        public DataSourceParams build() {
+            return new DataSourceParams(
+                    this.mBufferExhaustedPolicy, this.mWillNotifyOnStop, this.mNoFlush);
+        }
+
+        private @PerfettoDsBufferExhausted int mBufferExhaustedPolicy =
+                PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP;
+        private boolean mWillNotifyOnStop = true;
+        private boolean mNoFlush = false;
+    }
 }
diff --git a/core/java/android/tracing/perfetto/TracingContext.java b/core/java/android/tracing/perfetto/TracingContext.java
index 6b7df54..98cb4c8 100644
--- a/core/java/android/tracing/perfetto/TracingContext.java
+++ b/core/java/android/tracing/perfetto/TracingContext.java
@@ -59,19 +59,6 @@
     }
 
     /**
-     * Forces a commit of the thread-local tracing data written so far to the
-     * service. This is almost never required (tracing data is periodically
-     * committed as trace pages are filled up) and has a non-negligible
-     * performance hit (requires an IPC + refresh of the current thread-local
-     * chunk). The only case when this should be used is when handling OnStop()
-     * asynchronously, to ensure sure that the data is committed before the
-     * Stop timeout expires.
-     */
-    public void flush() {
-        nativeFlush(mDataSource.mNativeObj, getAndClearAllPendingTracePackets());
-    }
-
-    /**
      * Can optionally be used to store custom per-sequence
      * session data, which is not reset when incremental state is cleared
      * (e.g. configuration options).
@@ -109,7 +96,7 @@
         return incrementalState;
     }
 
-    private byte[][] getAndClearAllPendingTracePackets() {
+    protected byte[][] getAndClearAllPendingTracePackets() {
         byte[][] res = new byte[mTracePackets.size()][];
         for (int i = 0; i < mTracePackets.size(); i++) {
             ProtoOutputStream tracePacket = mTracePackets.get(i);
@@ -120,8 +107,6 @@
         return res;
     }
 
-    private static native void nativeFlush(long dataSourcePtr, byte[][] packetData);
-
     private static native Object nativeGetCustomTls(long nativeDsPtr);
     private static native void nativeSetCustomTls(long nativeDsPtr, Object tlsState);
 
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index db665a9..c4becea 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -1392,10 +1392,6 @@
 
     private static Rect computeSafeInsets(int displayW, int displayH, Insets waterFallInsets,
             Rect[] bounds) {
-        if (displayW == displayH) {
-            throw new UnsupportedOperationException("not implemented: display=" + displayW + "x"
-                    + displayH + " bounding rects=" + Arrays.toString(bounds));
-        }
 
         int leftInset = Math.max(waterFallInsets.left, findCutoutInsetForSide(
                 displayW, displayH, bounds[BOUNDS_POSITION_LEFT], Gravity.LEFT));
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index 35b137a..c5b6aa7 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -129,7 +129,7 @@
         }
         ViewGroup effective = null;
         ViewParent nextParent = focused.getParent();
-        do {
+        while (nextParent instanceof ViewGroup) {
             if (nextParent == root) {
                 return effective != null ? effective : root;
             }
@@ -143,7 +143,7 @@
                 effective = vg;
             }
             nextParent = nextParent.getParent();
-        } while (nextParent instanceof ViewGroup);
+        }
         return root;
     }
 
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 9ff29a8..4837ee5 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -26,11 +26,13 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UiContext;
+import android.app.Activity;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
 import android.os.StrictMode;
 import android.os.SystemClock;
@@ -299,6 +301,11 @@
     private VelocityTracker mVelocityTracker;
 
     /**
+     * Determines strategy for velocity calculation
+     */
+    private @VelocityTracker.VelocityTrackerStrategy int mVelocityTrackerStrategy;
+
+    /**
      * Consistency verifier for debugging purposes.
      */
     private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
@@ -347,17 +354,17 @@
 
     /**
      * Creates a GestureDetector with the supplied listener.
-     * This variant of the constructor should be used from a non-UI thread 
+     * This variant of the constructor should be used from a non-UI thread
      * (as it allows specifying the Handler).
-     * 
+     *
      * @param listener the listener invoked for all the callbacks, this must
      * not be null.
      * @param handler the handler to use
      *
      * @throws NullPointerException if {@code listener} is null.
      *
-     * @deprecated Use {@link #GestureDetector(android.content.Context,
-     *      android.view.GestureDetector.OnGestureListener, android.os.Handler)} instead.
+     * @deprecated Use {@link #GestureDetector(Context, GestureDetector.OnGestureListener, Handler)}
+     * instead.
      */
     @Deprecated
     public GestureDetector(@NonNull OnGestureListener listener, @Nullable Handler handler) {
@@ -367,15 +374,14 @@
     /**
      * Creates a GestureDetector with the supplied listener.
      * You may only use this constructor from a UI thread (this is the usual situation).
-     * @see android.os.Handler#Handler()
-     * 
+     * @see Handler#Handler()
+     *
      * @param listener the listener invoked for all the callbacks, this must
      * not be null.
-     * 
+     *
      * @throws NullPointerException if {@code listener} is null.
      *
-     * @deprecated Use {@link #GestureDetector(android.content.Context,
-     *      android.view.GestureDetector.OnGestureListener)} instead.
+     * @deprecated Use {@link #GestureDetector(Context, GestureDetector.OnGestureListener)} instead.
      */
     @Deprecated
     public GestureDetector(@NonNull OnGestureListener listener) {
@@ -384,10 +390,10 @@
 
     /**
      * Creates a GestureDetector with the supplied listener.
-     * You may only use this constructor from a {@link android.os.Looper} thread.
-     * @see android.os.Handler#Handler()
+     * You may only use this constructor from a {@link Looper} thread.
+     * @see Handler#Handler()
      *
-     * @param context An {@link android.app.Activity} or a {@link Context} created from
+     * @param context An {@link Activity} or a {@link Context} created from
      * {@link Context#createWindowContext(int, Bundle)}
      * @param listener the listener invoked for all the callbacks, this must
      * not be null. If the listener implements the {@link OnDoubleTapListener} or
@@ -404,10 +410,10 @@
 
     /**
      * Creates a GestureDetector with the supplied listener that runs deferred events on the
-     * thread associated with the supplied {@link android.os.Handler}.
-     * @see android.os.Handler#Handler()
+     * thread associated with the supplied {@link Handler}.
+     * @see Handler#Handler()
      *
-     * @param context An {@link android.app.Activity} or a {@link Context} created from
+     * @param context An {@link Activity} or a {@link Context} created from
      * {@link Context#createWindowContext(int, Bundle)}
      * @param listener the listener invoked for all the callbacks, this must
      * not be null. If the listener implements the {@link OnDoubleTapListener} or
@@ -419,6 +425,31 @@
      */
     public GestureDetector(@Nullable @UiContext Context context,
             @NonNull OnGestureListener listener, @Nullable Handler handler) {
+        this(context, listener, handler, VelocityTracker.VELOCITY_TRACKER_STRATEGY_DEFAULT);
+    }
+
+    /**
+     * Creates a GestureDetector with the supplied listener that runs deferred events on the
+     * thread associated with the supplied {@link Handler}.
+     * @see Handler#Handler()
+     *
+     * @param context An {@link Activity} or a {@link Context} created from
+     * {@link Context#createWindowContext(int, Bundle)}
+     * @param listener the listener invoked for all the callbacks, this must
+     * not be null. If the listener implements the {@link OnDoubleTapListener} or
+     * {@link OnContextClickListener} then it will also be set as the listener for
+     * these callbacks (for example when using the {@link SimpleOnGestureListener}).
+     * @param handler the handler to use for running deferred listener events.
+     * @param velocityTrackerStrategy strategy to use for velocity calculation of scroll/fling
+     *                                events.
+     *
+     * @throws NullPointerException if {@code listener} is null.
+     *
+     * @hide
+     */
+    public GestureDetector(@Nullable @UiContext Context context,
+            @NonNull OnGestureListener listener, @Nullable Handler handler,
+            @VelocityTracker.VelocityTrackerStrategy int velocityTrackerStrategy) {
         if (handler != null) {
             mHandler = new GestureHandler(handler);
         } else {
@@ -431,15 +462,16 @@
         if (listener instanceof OnContextClickListener) {
             setContextClickListener((OnContextClickListener) listener);
         }
+        mVelocityTrackerStrategy = velocityTrackerStrategy;
         init(context);
     }
-    
+
     /**
      * Creates a GestureDetector with the supplied listener that runs deferred events on the
-     * thread associated with the supplied {@link android.os.Handler}.
-     * @see android.os.Handler#Handler()
+     * thread associated with the supplied {@link Handler}.
+     * @see Handler#Handler()
      *
-     * @param context An {@link android.app.Activity} or a {@link Context} created from
+     * @param context An {@link Activity} or a {@link Context} created from
      * {@link Context#createWindowContext(int, Bundle)}
      * @param listener the listener invoked for all the callbacks, this must
      * not be null.
@@ -547,7 +579,7 @@
         mCurrentMotionEvent = MotionEvent.obtain(ev);
 
         if (mVelocityTracker == null) {
-            mVelocityTracker = VelocityTracker.obtain();
+            mVelocityTracker = VelocityTracker.obtain(mVelocityTrackerStrategy);
         }
         mVelocityTracker.addMovement(ev);
 
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 1c0834f..3743035 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -108,11 +108,6 @@
     void dispatchDragEvent(in DragEvent event);
 
     /**
-     * Pointer icon events
-     */
-    void updatePointerIcon(float x, float y);
-
-    /**
      * Called for non-application windows when the enter animation has completed.
      */
     void dispatchWindowShown();
@@ -128,4 +123,9 @@
      * @param callbacks to receive responses
      */
     void requestScrollCapture(in IScrollCaptureResponseListener callbacks);
+
+    /**
+     * Dump the details of a window.
+     */
+    void dumpWindow(in ParcelFileDescriptor pfd);
 }
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 86264eb..e3e4fc0 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -288,8 +288,6 @@
 
     oneway void finishMovingTask(IWindow window);
 
-    oneway void updatePointerIcon(IWindow window);
-
     /**
      * Update a tap exclude region identified by provided id in the window. Touches on this region
      * will neither be dispatched to this window nor change the focus to this window. Passing an
diff --git a/core/java/android/view/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java
index 1afedc1..8c14de6 100644
--- a/core/java/android/view/ImeBackAnimationController.java
+++ b/core/java/android/view/ImeBackAnimationController.java
@@ -38,6 +38,8 @@
 
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 
+import java.io.PrintWriter;
+
 /**
  * Controller for IME predictive back animation
  *
@@ -134,7 +136,9 @@
 
     @Override
     public void onBackInvoked() {
-        if (!isBackAnimationAllowed()) {
+        if (!isBackAnimationAllowed() || !mIsPreCommitAnimationInProgress) {
+            // play regular hide animation if back-animation is not allowed or if insets control has
+            // been cancelled by the system (this can happen in split screen for example)
             mInsetsController.hide(ime());
             return;
         }
@@ -206,18 +210,9 @@
             mInsetsController.setPredictiveBackImeHideAnimInProgress(true);
             notifyHideIme();
         }
-        if (mStartRootScrollY != 0) {
-            // RootView is panned, ensure that it is scrolled back to the intended scroll position
-            if (triggerBack) {
-                // requesting ime as invisible
-                mInsetsController.setRequestedVisibleTypes(0, ime());
-                // changes the animation state and notifies RootView of changed insets, which
-                // causes it to reset its scrollY to 0f (animated)
-                mInsetsController.onAnimationStateChanged(ime(), /*running*/ true);
-            } else {
-                // This causes RootView to update its scroll back to the panned position
-                mInsetsController.getHost().notifyInsetsChanged();
-            }
+        if (mStartRootScrollY != 0 && !triggerBack) {
+            // This causes RootView to update its scroll back to the panned position
+            mInsetsController.getHost().notifyInsetsChanged();
         }
     }
 
@@ -233,6 +228,12 @@
         // the IME away
         mInsetsController.getHost().getInputMethodManager()
                 .notifyImeHidden(mInsetsController.getHost().getWindowToken(), statsToken);
+
+        // requesting IME as invisible during post-commit
+        mInsetsController.setRequestedVisibleTypes(0, ime());
+        // Changes the animation state. This also notifies RootView of changed insets, which causes
+        // it to reset its scrollY to 0f (animated) if it was panned
+        mInsetsController.onAnimationStateChanged(ime(), /*running*/ true);
     }
 
     private void reset() {
@@ -269,4 +270,24 @@
         return mPostCommitAnimator != null && mTriggerBack;
     }
 
+    /**
+     * Dump information about this ImeBackAnimationController
+     *
+     * @param prefix the prefix that will be prepended to each line of the produced output
+     * @param writer the writer that will receive the resulting text
+     */
+    public void dump(String prefix, PrintWriter writer) {
+        final String innerPrefix = prefix + "    ";
+        writer.println(prefix + "ImeBackAnimationController:");
+        writer.println(innerPrefix + "mLastProgress=" + mLastProgress);
+        writer.println(innerPrefix + "mTriggerBack=" + mTriggerBack);
+        writer.println(innerPrefix + "mIsPreCommitAnimationInProgress="
+                + mIsPreCommitAnimationInProgress);
+        writer.println(innerPrefix + "mStartRootScrollY=" + mStartRootScrollY);
+        writer.println(innerPrefix + "isBackAnimationAllowed=" + isBackAnimationAllowed());
+        writer.println(innerPrefix + "isAdjustPan=" + isAdjustPan());
+        writer.println(innerPrefix + "isHideAnimationInProgress="
+                + isHideAnimationInProgress());
+    }
+
 }
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index c545267..d7f2b01 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1262,10 +1262,13 @@
                     mHost.getInputMethodManager(), null /* icProto */);
         }
 
+        final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_USER,
+                ImeTracker.ORIGIN_CLIENT, SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION,
+                mHost.isHandlingPointerEvent() /* fromUser */);
         controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs,
                 interpolator, animationType,
                 getLayoutInsetsDuringAnimationMode(types, fromPredictiveBack),
-                false /* useInsetsAnimationThread */, null /* statsToken */);
+                false /* useInsetsAnimationThread */, statsToken);
     }
 
     private void controlAnimationUnchecked(@InsetsType int types,
@@ -1567,7 +1570,9 @@
             return;
         }
         final ImeTracker.Token statsToken = runner.getStatsToken();
-        if (shown) {
+        if (runner.getAnimationType() == ANIMATION_TYPE_USER) {
+            ImeTracker.forLogging().onUserFinished(statsToken, shown);
+        } else if (shown) {
             ImeTracker.forLogging().onProgress(statsToken,
                     ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_SHOW);
             ImeTracker.forLogging().onShown(statsToken);
@@ -1799,8 +1804,11 @@
     }
 
     void dump(String prefix, PrintWriter pw) {
-        pw.print(prefix); pw.println("InsetsController:");
-        mState.dump(prefix + "  ", pw);
+        final String innerPrefix = prefix + "    ";
+        pw.println(prefix + "InsetsController:");
+        mState.dump(innerPrefix, pw);
+        pw.println(innerPrefix + "mIsPredictiveBackImeHideAnimInProgress="
+                + mIsPredictiveBackImeHideAnimInProgress);
     }
 
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
@@ -1835,6 +1843,9 @@
             Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.pendingAnim", 0);
             mHost.dispatchWindowInsetsAnimationStart(animation, bounds);
             mStartingAnimation = true;
+            if (runner.getAnimationType() == ANIMATION_TYPE_USER) {
+                ImeTracker.forLogging().onDispatched(runner.getStatsToken());
+            }
             runner.setReadyDispatched(true);
             listener.onReady(runner, types);
             mStartingAnimation = false;
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index 7dc151d..71199e9 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -234,7 +234,7 @@
         }
 
         int typeIndex = getSystemIconTypeIndex(type);
-        if (typeIndex == 0) {
+        if (typeIndex < 0) {
             typeIndex = getSystemIconTypeIndex(TYPE_DEFAULT);
         }
 
@@ -606,7 +606,7 @@
             case TYPE_HANDWRITING:
                 return com.android.internal.R.styleable.Pointer_pointerIconHandwriting;
             default:
-                return 0;
+                return -1;
         }
     }
 
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index a23df79..1d70d18 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -986,7 +986,7 @@
 
             updateBackgroundVisibility(surfaceUpdateTransaction);
             updateBackgroundColor(surfaceUpdateTransaction);
-            if (mLimitedHdrEnabled && hdrHeadroomChanged) {
+            if (mLimitedHdrEnabled && (hdrHeadroomChanged || creating)) {
                 surfaceUpdateTransaction.setDesiredHdrHeadroom(
                         mBlastSurfaceControl, mHdrHeadroom);
             }
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index d31f823..27176a4 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -264,7 +264,6 @@
 
     /**
      * Obtains a velocity tracker with the specified strategy.
-     * For testing and comparison purposes only.
      *
      * @param strategy The strategy Id, VELOCITY_TRACKER_STRATEGY_DEFAULT to use the default.
      * @return The velocity tracker.
@@ -272,6 +271,9 @@
      * @hide
      */
     public static VelocityTracker obtain(int strategy) {
+        if (strategy == VELOCITY_TRACKER_STRATEGY_DEFAULT) {
+            return obtain();
+        }
         return new VelocityTracker(strategy);
     }
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9579614..1cb2765 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -40,7 +40,6 @@
 import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
 import static android.view.flags.Flags.enableUseMeasureCacheDuringForceLayout;
 import static android.view.flags.Flags.sensitiveContentAppProtection;
-import static android.view.flags.Flags.sensitiveContentPrematureProtectionRemovedFix;
 import static android.view.flags.Flags.toolkitFrameRateBySizeReadOnly;
 import static android.view.flags.Flags.toolkitFrameRateDefaultNormalReadOnly;
 import static android.view.flags.Flags.toolkitFrameRateSmallUsesPercentReadOnly;
@@ -30696,21 +30695,11 @@
      */
     public void setPointerIcon(PointerIcon pointerIcon) {
         mMousePointerIcon = pointerIcon;
-        if (com.android.input.flags.Flags.enablePointerChoreographer()) {
-            final ViewRootImpl viewRootImpl = getViewRootImpl();
-            if (viewRootImpl == null) {
-                return;
-            }
-            viewRootImpl.refreshPointerIcon();
-        } else {
-            if (mAttachInfo == null || mAttachInfo.mHandlingPointerEvent) {
-                return;
-            }
-            try {
-                mAttachInfo.mSession.updatePointerIcon(mAttachInfo.mWindow);
-            } catch (RemoteException e) {
-            }
+        final ViewRootImpl viewRootImpl = getViewRootImpl();
+        if (viewRootImpl == null) {
+            return;
         }
+        viewRootImpl.refreshPointerIcon();
     }
 
     /**
@@ -32230,7 +32219,7 @@
 
         void increaseSensitiveViewsCount() {
             if (mSensitiveViewsCount == 0) {
-                mViewRootImpl.notifySensitiveContentAppProtection(true);
+                mViewRootImpl.addSensitiveContentAppProtection();
             }
             mSensitiveViewsCount++;
         }
@@ -32238,11 +32227,7 @@
         void decreaseSensitiveViewsCount() {
             mSensitiveViewsCount--;
             if (mSensitiveViewsCount == 0) {
-                if (sensitiveContentPrematureProtectionRemovedFix()) {
-                    mViewRootImpl.removeSensitiveContentProtectionOnTransactionCommit();
-                } else {
-                    mViewRootImpl.notifySensitiveContentAppProtection(false);
-                }
+                mViewRootImpl.removeSensitiveContentAppProtection();
             }
             if (mSensitiveViewsCount < 0) {
                 Log.wtf(VIEW_LOG_TAG, "mSensitiveViewsCount is negative" + mSensitiveViewsCount);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1d84375..0715474 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -25,6 +25,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.DragEvent.ACTION_DRAG_LOCATION;
+import static android.view.flags.Flags.sensitiveContentPrematureProtectionRemovedFix;
 import static android.view.InputDevice.SOURCE_CLASS_NONE;
 import static android.view.InsetsSource.ID_IME;
 import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
@@ -73,7 +74,6 @@
 import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES;
 import static android.view.ViewRootImplProto.WIN_FRAME;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -95,6 +95,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -121,7 +122,6 @@
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
 
-import static com.android.input.flags.Flags.enablePointerChoreographer;
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.window.flags.Flags.activityWindowInfoFlag;
 import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay;
@@ -194,6 +194,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.StrictMode;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.Trace;
@@ -267,11 +268,15 @@
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.policy.PhoneFallbackEventHandler;
+import com.android.internal.util.FastPrintWriter;
 import com.android.internal.view.BaseSurfaceHolder;
 import com.android.internal.view.RootViewSurfaceTaker;
 import com.android.internal.view.SurfaceCallbackHelper;
 import com.android.modules.expresslog.Counter;
 
+import libcore.io.IoUtils;
+
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.PrintWriter;
@@ -916,15 +921,6 @@
     private String mFrameRateCategoryView;
 
     /**
-     * The resolved pointer icon type requested by this window.
-     * A null value indicates the resolved pointer icon has not yet been calculated.
-     */
-    // TODO(b/293587049): Remove pointer icon tracking by type when refactor is complete.
-    @Nullable
-    private Integer mPointerIconType = null;
-    private PointerIcon mCustomPointerIcon = null;
-
-    /**
      * The resolved pointer icon requested by this window.
      * A null value indicates the resolved pointer icon has not yet been calculated.
      */
@@ -4244,7 +4240,14 @@
             mReportNextDraw = false;
             mLastReportNextDrawReason = null;
             mActiveSurfaceSyncGroup = null;
-            mHasPendingTransactions = false;
+            if (mHasPendingTransactions) {
+                // TODO: We shouldn't ever actually hit this, it means mPendingTransaction wasn't
+                // merged with a sync group or BLASTBufferQueue before making it to this point
+                // But better a one or two frame flicker than steady-state broken from dropping
+                // whatever is in this transaction
+                mPendingTransaction.apply();
+                mHasPendingTransactions = false;
+            }
             mSyncBuffer = false;
             if (isInWMSRequestedSync()) {
                 mWmsRequestSyncGroup.markSyncReady();
@@ -4331,29 +4334,42 @@
      *   <li>It should only notify service to unblock projection when all sensitive view are
      *   removed from the window.
      * </ol>
+     *
+     * @param enableProtection if true, the protection is enabled for this window.
+     *                         if false, the protection is removed for this window.
      */
-    void notifySensitiveContentAppProtection(boolean showSensitiveContent) {
+    private void applySensitiveContentAppProtection(boolean enableProtection) {
         try {
             if (mSensitiveContentProtectionService == null) {
                 return;
             }
             if (DEBUG_SENSITIVE_CONTENT) {
                 Log.d(TAG, "Notify sensitive content, package=" + mContext.getPackageName()
-                        + ", token=" + getWindowToken() + ", flag=" + showSensitiveContent);
+                        + ", token=" + getWindowToken() + ", flag=" + enableProtection);
             }
             // The window would be blocked during screen share if it shows sensitive content.
             mSensitiveContentProtectionService.setSensitiveContentProtection(
-                    getWindowToken(), mContext.getPackageName(), showSensitiveContent);
+                    getWindowToken(), mContext.getPackageName(), enableProtection);
         } catch (RemoteException ex) {
             Log.e(TAG, "Unable to protect sensitive content during screen share", ex);
         }
     }
 
     /**
-     * Sensitive protection is removed on transaction commit to avoid prematurely removing
-     * the protection.
+     * Add sensitive content protection, when there are one or more visible sensitive views.
      */
-    void removeSensitiveContentProtectionOnTransactionCommit() {
+    void addSensitiveContentAppProtection() {
+        applySensitiveContentAppProtection(true);
+    }
+
+    /**
+     * Remove sensitive content protection, when there is no visible sensitive view.
+     */
+    void removeSensitiveContentAppProtection() {
+        if (!sensitiveContentPrematureProtectionRemovedFix()) {
+            applySensitiveContentAppProtection(false);
+            return;
+        }
         if (DEBUG_SENSITIVE_CONTENT) {
             Log.d(TAG, "Add transaction to remove sensitive content protection, package="
                     + mContext.getPackageName() + ", token=" + getWindowToken());
@@ -4361,7 +4377,7 @@
         Transaction t = new Transaction();
         t.addTransactionCommittedListener(mExecutor, () -> {
             if (mAttachInfo.mSensitiveViewsCount == 0) {
-                notifySensitiveContentAppProtection(false);
+                applySensitiveContentAppProtection(false);
             }
         });
         applyTransactionOnDraw(t);
@@ -5279,10 +5295,12 @@
         if (DEBUG_CONTENT_CAPTURE) {
             Log.v(mTag, "performContentCaptureInitialReport() on " + rootView);
         }
+        boolean traceDispatchCapture = false;
         try {
             if (!isContentCaptureEnabled()) return;
 
-            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            traceDispatchCapture = Trace.isTagEnabled(Trace.TRACE_TAG_VIEW);
+            if (traceDispatchCapture) {
                 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "dispatchContentCapture() for "
                         + getClass().getSimpleName());
             }
@@ -5298,7 +5316,9 @@
             // Content capture is a go!
             rootView.dispatchInitialProvideContentCaptureStructure();
         } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            if (traceDispatchCapture) {
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
         }
     }
 
@@ -5306,10 +5326,12 @@
         if (DEBUG_CONTENT_CAPTURE) {
             Log.v(mTag, "handleContentCaptureFlush()");
         }
+        boolean traceFlushContentCapture = false;
         try {
             if (!isContentCaptureEnabled()) return;
 
-            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            traceFlushContentCapture = Trace.isTagEnabled(Trace.TRACE_TAG_VIEW);
+            if (traceFlushContentCapture) {
                 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "flushContentCapture for "
                         + getClass().getSimpleName());
             }
@@ -5321,7 +5343,9 @@
             }
             ccm.flush(ContentCaptureSession.FLUSH_REASON_VIEW_ROOT_ENTERED);
         } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            if (traceFlushContentCapture) {
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
         }
     }
 
@@ -6397,7 +6421,6 @@
     private static final int MSG_SYNTHESIZE_INPUT_EVENT = 24;
     private static final int MSG_DISPATCH_WINDOW_SHOWN = 25;
     private static final int MSG_REQUEST_KEYBOARD_SHORTCUTS = 26;
-    private static final int MSG_UPDATE_POINTER_ICON = 27;
     private static final int MSG_POINTER_CAPTURE_CHANGED = 28;
     private static final int MSG_INSETS_CONTROL_CHANGED = 29;
     private static final int MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED = 30;
@@ -6462,8 +6485,6 @@
                     return "MSG_SYNTHESIZE_INPUT_EVENT";
                 case MSG_DISPATCH_WINDOW_SHOWN:
                     return "MSG_DISPATCH_WINDOW_SHOWN";
-                case MSG_UPDATE_POINTER_ICON:
-                    return "MSG_UPDATE_POINTER_ICON";
                 case MSG_POINTER_CAPTURE_CHANGED:
                     return "MSG_POINTER_CAPTURE_CHANGED";
                 case MSG_INSETS_CONTROL_CHANGED:
@@ -6714,10 +6735,6 @@
                     final int deviceId = msg.arg1;
                     handleRequestKeyboardShortcuts(receiver, deviceId);
                 } break;
-                case MSG_UPDATE_POINTER_ICON: {
-                    MotionEvent event = (MotionEvent) msg.obj;
-                    resetPointerIcon(event);
-                } break;
                 case MSG_POINTER_CAPTURE_CHANGED: {
                     final boolean hasCapture = msg.arg1 != 0;
                     handlePointerCaptureChanged(hasCapture);
@@ -7771,6 +7788,7 @@
                     mWindowAttributes.type)) {
                 // set the frame rate to the maximum value.
                 mIsTouchBoosting = true;
+                setPreferredFrameRateCategory(mLastPreferredFrameRateCategory);
             }
             /**
              * We want to lower the refresh rate when MotionEvent.ACTION_UP,
@@ -7802,14 +7820,12 @@
                     || action == MotionEvent.ACTION_HOVER_EXIT) {
                 // Other apps or the window manager may change the icon type outside of
                 // this app, therefore the icon type has to be reset on enter/exit event.
-                mPointerIconType = null;
                 mResolvedPointerIcon = null;
             }
 
             if (action != MotionEvent.ACTION_HOVER_EXIT) {
                 // Resolve the pointer icon
                 if (!updatePointerIcon(event) && action == MotionEvent.ACTION_HOVER_MOVE) {
-                    mPointerIconType = null;
                     mResolvedPointerIcon = null;
                 }
             }
@@ -7872,12 +7888,6 @@
         return mAttachInfo.mHandlingPointerEvent;
     }
 
-    private void resetPointerIcon(MotionEvent event) {
-        mPointerIconType = null;
-        mResolvedPointerIcon = null;
-        updatePointerIcon(event);
-    }
-
 
     /**
      * If there is pointer that is showing a PointerIcon in this window, refresh the icon for that
@@ -7907,46 +7917,20 @@
         if (event.isStylusPointer() && mIsStylusPointerIconEnabled) {
             pointerIcon = mHandwritingInitiator.onResolvePointerIcon(mContext, event);
         }
-
         if (pointerIcon == null) {
             pointerIcon = mView.onResolvePointerIcon(event, pointerIndex);
         }
-
-        if (enablePointerChoreographer()) {
-            if (pointerIcon == null) {
-                pointerIcon = PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED);
-            }
-            if (Objects.equals(mResolvedPointerIcon, pointerIcon)) {
-                return true;
-            }
-            mResolvedPointerIcon = pointerIcon;
-
-            InputManagerGlobal.getInstance()
-                    .setPointerIcon(pointerIcon, event.getDisplayId(),
-                            event.getDeviceId(), event.getPointerId(0), getInputToken());
+        if (pointerIcon == null) {
+            pointerIcon = PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED);
+        }
+        if (Objects.equals(mResolvedPointerIcon, pointerIcon)) {
             return true;
         }
+        mResolvedPointerIcon = pointerIcon;
 
-        final int pointerType = (pointerIcon != null) ?
-                pointerIcon.getType() : PointerIcon.TYPE_NOT_SPECIFIED;
-
-        if (mPointerIconType == null || mPointerIconType != pointerType) {
-            mPointerIconType = pointerType;
-            mCustomPointerIcon = null;
-            if (mPointerIconType != PointerIcon.TYPE_CUSTOM) {
-                InputManagerGlobal
-                        .getInstance()
-                        .setPointerIconType(pointerType);
-                return true;
-            }
-        }
-        if (mPointerIconType == PointerIcon.TYPE_CUSTOM &&
-                !pointerIcon.equals(mCustomPointerIcon)) {
-            mCustomPointerIcon = pointerIcon;
-            InputManagerGlobal
-                    .getInstance()
-                    .setCustomPointerIcon(mCustomPointerIcon);
-        }
+        InputManagerGlobal.getInstance()
+                .setPointerIcon(pointerIcon, event.getDisplayId(),
+                        event.getDeviceId(), event.getPointerId(0), getInputToken());
         return true;
     }
 
@@ -8563,48 +8547,55 @@
 
         private int mPendingKeyMetaState;
 
-        private final GestureDetector mGestureDetector = new GestureDetector(mContext,
-                new GestureDetector.OnGestureListener() {
-                    @Override
-                    public boolean onDown(@NonNull MotionEvent e) {
-                        // This can be ignored since it's not clear what KeyEvent this will
-                        // belong to.
-                        return true;
-                    }
-
-                    @Override
-                    public void onShowPress(@NonNull MotionEvent e) {
-
-                    }
-
-                    @Override
-                    public boolean onSingleTapUp(@NonNull MotionEvent e) {
-                        dispatchTap(e.getEventTime());
-                        return true;
-                    }
-
-                    @Override
-                    public boolean onScroll(@Nullable MotionEvent e1, @NonNull MotionEvent e2,
-                            float distanceX, float distanceY) {
-                        // Scroll doesn't translate to DPAD events so should be ignored.
-                        return true;
-                    }
-
-                    @Override
-                    public void onLongPress(@NonNull MotionEvent e) {
-                        // Long presses don't translate to DPAD events so should be ignored.
-                    }
-
-                    @Override
-                    public boolean onFling(@Nullable MotionEvent e1, @NonNull MotionEvent e2,
-                            float velocityX, float velocityY) {
-                        dispatchFling(velocityX, velocityY, e2.getEventTime());
-                        return true;
-                    }
-                });
+        private final GestureDetector mGestureDetector;
 
         SyntheticTouchNavigationHandler() {
             super(true);
+            int gestureDetectorVelocityStrategy =
+                    android.companion.virtual.flags.Flags
+                            .impulseVelocityStrategyForTouchNavigation()
+                    ? VelocityTracker.VELOCITY_TRACKER_STRATEGY_IMPULSE
+                    : VelocityTracker.VELOCITY_TRACKER_STRATEGY_DEFAULT;
+            mGestureDetector = new GestureDetector(mContext,
+                    new GestureDetector.OnGestureListener() {
+                        @Override
+                        public boolean onDown(@NonNull MotionEvent e) {
+                            // This can be ignored since it's not clear what KeyEvent this will
+                            // belong to.
+                            return true;
+                        }
+
+                        @Override
+                        public void onShowPress(@NonNull MotionEvent e) {
+                        }
+
+                        @Override
+                        public boolean onSingleTapUp(@NonNull MotionEvent e) {
+                            dispatchTap(e.getEventTime());
+                            return true;
+                        }
+
+                        @Override
+                        public boolean onScroll(@Nullable MotionEvent e1, @NonNull MotionEvent e2,
+                                float distanceX, float distanceY) {
+                            // Scroll doesn't translate to DPAD events so should be ignored.
+                            return true;
+                        }
+
+                        @Override
+                        public void onLongPress(@NonNull MotionEvent e) {
+                            // Long presses don't translate to DPAD events so should be ignored.
+                        }
+
+                        @Override
+                        public boolean onFling(@Nullable MotionEvent e1, @NonNull MotionEvent e2,
+                                float velocityX, float velocityY) {
+                            dispatchFling(velocityX, velocityY, e2.getEventTime());
+                            return true;
+                        }
+                    },
+                    /* handler= */ null,
+                    gestureDetectorVelocityStrategy);
         }
 
         public void process(MotionEvent event) {
@@ -9579,6 +9570,8 @@
 
         mOnBackInvokedDispatcher.dump(prefix, writer);
 
+        mImeBackAnimationController.dump(prefix, writer);
+
         writer.println(prefix + "View Hierarchy:");
         dumpViewHierarchy(innerPrefix, writer, mView);
     }
@@ -10548,16 +10541,6 @@
         mHandler.sendMessage(msg);
     }
 
-    public void updatePointerIcon(float x, float y) {
-        final int what = MSG_UPDATE_POINTER_ICON;
-        mHandler.removeMessages(what);
-        final long now = SystemClock.uptimeMillis();
-        final MotionEvent event = MotionEvent.obtain(
-                0, now, MotionEvent.ACTION_HOVER_MOVE, x, y, 0);
-        Message msg = mHandler.obtainMessage(what, event);
-        mHandler.sendMessage(msg);
-    }
-
     public void dispatchCheckFocus() {
         if (!mHandler.hasMessages(MSG_CHECK_FOCUS)) {
             // This will result in a call to checkFocus() below.
@@ -11421,14 +11404,6 @@
         }
 
         @Override
-        public void updatePointerIcon(float x, float y) {
-            final ViewRootImpl viewAncestor = mViewAncestor.get();
-            if (viewAncestor != null) {
-                viewAncestor.updatePointerIcon(x, y);
-            }
-        }
-
-        @Override
         public void dispatchWindowShown() {
             final ViewRootImpl viewAncestor = mViewAncestor.get();
             if (viewAncestor != null) {
@@ -11451,6 +11426,26 @@
                 viewAncestor.dispatchScrollCaptureRequest(listener);
             }
         }
+
+        @Override
+        public void dumpWindow(ParcelFileDescriptor pfd) {
+            final ViewRootImpl viewAncestor = mViewAncestor.get();
+            if (viewAncestor == null) {
+                return;
+            }
+            viewAncestor.mHandler.postAtFrontOfQueue(() -> {
+                final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+                try {
+                    PrintWriter pw = new FastPrintWriter(new FileOutputStream(
+                            pfd.getFileDescriptor()));
+                    viewAncestor.dump("", pw);
+                    pw.flush();
+                } finally {
+                    IoUtils.closeQuietly(pfd);
+                    StrictMode.setThreadPolicy(oldPolicy);
+                }
+            });
+        }
     }
 
     public static final class CalledFromWrongThreadException extends AndroidRuntimeException {
@@ -12635,10 +12630,12 @@
             view = mFrameRateCategoryView;
         }
 
+        boolean traceFrameRateCategory = false;
         try {
             if (frameRateCategory != FRAME_RATE_CATEGORY_DEFAULT
                     && mLastPreferredFrameRateCategory != frameRateCategory) {
-                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                traceFrameRateCategory = Trace.isTagEnabled(Trace.TRACE_TAG_VIEW);
+                if (traceFrameRateCategory) {
                     String reason = reasonToString(frameRateReason);
                     String sourceView = view == null ? "-" : view;
                     String category = categoryToString(frameRateCategory);
@@ -12656,7 +12653,9 @@
         } catch (Exception e) {
             Log.e(mTag, "Unable to set frame rate category", e);
         } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            if (traceFrameRateCategory) {
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
         }
     }
 
@@ -12696,9 +12695,11 @@
             return;
         }
 
+        boolean traceFrameRate = false;
         try {
             if (mLastPreferredFrameRate != preferredFrameRate) {
-                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                traceFrameRate = Trace.isTagEnabled(Trace.TRACE_TAG_VIEW);
+                if (traceFrameRate) {
                     Trace.traceBegin(
                             Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRate "
                                 + preferredFrameRate + " compatibility "
@@ -12713,7 +12714,9 @@
         } catch (Exception e) {
             Log.e(mTag, "Unable to set frame rate", e);
         } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            if (traceFrameRate) {
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
         }
     }
 
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index e6367ff..d7d764b 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -603,10 +603,6 @@
     }
 
     @Override
-    public void updatePointerIcon(android.view.IWindow window) {
-    }
-
-    @Override
     public void updateTapExcludeRegion(android.view.IWindow window,
             android.graphics.Region region) {
     }
diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java
index a2f3544..5aaa994 100644
--- a/core/java/android/view/animation/AnimationSet.java
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -374,7 +374,7 @@
             final Animation a = mAnimations.get(i);
 
             temp.clear();
-            a.getTransformationAt(interpolatedTime, t);
+            a.getTransformationAt(interpolatedTime, temp);
             t.compose(temp);
         }
     }
diff --git a/core/java/android/view/animation/Transformation.java b/core/java/android/view/animation/Transformation.java
index de31667..812ecd1 100644
--- a/core/java/android/view/animation/Transformation.java
+++ b/core/java/android/view/animation/Transformation.java
@@ -78,6 +78,7 @@
         mHasClipRect = false;
         mAlpha = 1.0f;
         mTransformationType = TYPE_BOTH;
+        mInsets = Insets.NONE;
     }
 
     /**
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index c7df15c..9cc4191 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1592,7 +1592,8 @@
                 // request comes in but PCC Detection hasn't been triggered. There is no benefit to
                 // trigger PCC Detection separately in those cases.
                 if (!isActiveLocked()) {
-                    final boolean clientAdded = tryAddServiceClientIfNeededLocked();
+                    final boolean clientAdded =
+                            tryAddServiceClientIfNeededLocked(isCredmanRequested);
                     if (clientAdded) {
                         startSessionLocked(/* id= */ AutofillId.NO_AUTOFILL_ID, /* bounds= */ null,
                             /* value= */ null, /* flags= */ FLAG_PCC_DETECTION);
@@ -1850,7 +1851,8 @@
             Rect bounds, AutofillValue value, int flags) {
         if (shouldIgnoreViewEnteredLocked(id, flags)) return null;
 
-        final boolean clientAdded = tryAddServiceClientIfNeededLocked();
+        boolean credmanRequested = isCredmanRequested(view);
+        final boolean clientAdded = tryAddServiceClientIfNeededLocked(credmanRequested);
         if (!clientAdded) {
             if (sVerbose) Log.v(TAG, "ignoring notifyViewEntered(" + id + "): no service client");
             return null;
@@ -1976,6 +1978,13 @@
 
                     if (Objects.equals(mLastAutofilledData.get(id), value)) {
                         view.setAutofilled(true, hideHighlight);
+                        try {
+                            mService.setViewAutofilled(mSessionId, id, mContext.getUserId());
+                        } catch (RemoteException e) {
+                            // The failure could be a consequence of something going wrong on the
+                            // server side. Do nothing here since it's just logging, but it's
+                            // possible follow-up actions may fail.
+                        }
                     } else {
                         view.setAutofilled(false, false);
                         mLastAutofilledData.remove(id);
@@ -2645,6 +2654,11 @@
      */
     @GuardedBy("mLock")
     private boolean tryAddServiceClientIfNeededLocked() {
+        return tryAddServiceClientIfNeededLocked(/*credmanRequested=*/ false);
+    }
+
+    @GuardedBy("mLock")
+    private boolean tryAddServiceClientIfNeededLocked(boolean credmanRequested) {
         final AutofillClient client = getClient();
         if (client == null) {
             return false;
@@ -2659,7 +2673,7 @@
                 final int userId = mContext.getUserId();
                 final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
                 mService.addClient(mServiceClient, client.autofillClientGetComponentName(),
-                        userId, receiver);
+                        userId, receiver, credmanRequested);
                 int flags = 0;
                 try {
                     flags = receiver.getIntResult();
@@ -2971,6 +2985,13 @@
                 mLastAutofilledData.put(view.getAutofillId(), targetValue);
             }
             view.setAutofilled(true, hideHighlight);
+            try {
+                mService.setViewAutofilled(mSessionId, view.getAutofillId(), mContext.getUserId());
+            } catch (RemoteException e) {
+                // The failure could be a consequence of something going wrong on the server side.
+                // Do nothing here since it's just logging, but it's possible follow-up actions may
+                // fail.
+            }
         }
     }
 
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index cefd6dc..2039b4d 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -38,7 +38,7 @@
 oneway interface IAutoFillManager {
     // Returns flags: FLAG_ADD_CLIENT_ENABLED | FLAG_ADD_CLIENT_DEBUG | FLAG_ADD_CLIENT_VERBOSE
     void addClient(in IAutoFillManagerClient client, in ComponentName componentName, int userId,
-        in IResultReceiver result);
+        in IResultReceiver result, boolean credmanRequested);
     void removeClient(in IAutoFillManagerClient client, int userId);
     void startSession(IBinder activityToken, in IBinder appCallback, in AutofillId autoFillId,
         in Rect bounds, in AutofillValue value, int userId, boolean hasCallback, int flags,
@@ -49,6 +49,7 @@
     void updateSession(int sessionId, in AutofillId id, in Rect bounds,
         in AutofillValue value, int action, int flags, int userId);
     void setAutofillFailure(int sessionId, in List<AutofillId> ids, int userId);
+    void setViewAutofilled(int sessionId, in AutofillId id, int userId);
     void finishSession(int sessionId, int userId, int commitReason);
     void cancelSession(int sessionId, int userId);
     void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId, int userId);
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index 12bd45a..c0d31fa 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -54,7 +54,7 @@
   is_fixed_read_only: true
   metadata {
       purpose: PURPOSE_BUGFIX
-    }
+  }
 }
 
 flag {
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index c5114b9..fb3e083 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -720,6 +720,20 @@
 
     private boolean mIsStylusHandwritingEnabled;
 
+
+    /**
+     * AndroidX Core library 1.13.0 introduced EditorInfoCompat#setStylusHandwritingEnabled and
+     * EditorInfoCompat#isStylusHandwritingEnabled which used a boolean value in the EditorInfo
+     * extras bundle. These methods do not set or check the Android V property since the Android V
+     * SDK was not yet available. In order for EditorInfoCompat#isStylusHandwritingEnabled to return
+     * the correct value for EditorInfo created by Android V TextView, the extras bundle value
+     * should be set. This is the extras bundle key.
+     *
+     * @hide
+     */
+    public static final String STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY =
+            "androidx.core.view.inputmethod.EditorInfoCompat.STYLUS_HANDWRITING_ENABLED";
+
     /**
      * Set {@code true} if the {@code Editor} has
      * {@link InputMethodManager#startStylusHandwriting stylus handwriting} enabled.
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index f454a6a..3091bf4 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -754,6 +754,19 @@
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onDispatched */
+    static void onDispatched(@NonNull ImeTracker.Token statsToken) {
+        final IImeTracker service = getImeTrackerService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.onDispatched(statsToken);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /** @see com.android.server.inputmethod.ImeTrackerService#hasPendingImeVisibilityRequests */
     @AnyThread
     @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index d992feb..edc9921 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -71,24 +71,40 @@
     /** The type of the IME request. */
     @IntDef(prefix = { "TYPE_" }, value = {
             TYPE_SHOW,
-            TYPE_HIDE
+            TYPE_HIDE,
+            TYPE_USER,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface Type {}
 
-    /** IME show request type. */
+    /**
+     * IME show request type.
+     *
+     * @see android.view.InsetsController#ANIMATION_TYPE_SHOW
+     */
     int TYPE_SHOW = ImeProtoEnums.TYPE_SHOW;
 
-    /** IME hide request type. */
+    /**
+     * IME hide request type.
+     *
+     * @see android.view.InsetsController#ANIMATION_TYPE_HIDE
+     */
     int TYPE_HIDE = ImeProtoEnums.TYPE_HIDE;
 
+    /**
+     * IME user-controlled animation request type.
+     *
+     * @see android.view.InsetsController#ANIMATION_TYPE_USER
+     */
+    int TYPE_USER = ImeProtoEnums.TYPE_USER;
+
     /** The status of the IME request. */
     @IntDef(prefix = { "STATUS_" }, value = {
             STATUS_RUN,
             STATUS_CANCEL,
             STATUS_FAIL,
             STATUS_SUCCESS,
-            STATUS_TIMEOUT
+            STATUS_TIMEOUT,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface Status {}
@@ -117,7 +133,7 @@
     @IntDef(prefix = { "ORIGIN_" }, value = {
             ORIGIN_CLIENT,
             ORIGIN_SERVER,
-            ORIGIN_IME
+            ORIGIN_IME,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface Origin {}
@@ -400,20 +416,36 @@
     void onCancelled(@Nullable Token token, @Phase int phase);
 
     /**
-     * Called when the IME show request is successful.
+     * Called when the show IME request is successful.
      *
      * @param token the token tracking the current IME request or {@code null} otherwise.
      */
     void onShown(@Nullable Token token);
 
     /**
-     * Called when the IME hide request is successful.
+     * Called when the hide IME request is successful.
      *
      * @param token the token tracking the current IME request or {@code null} otherwise.
      */
     void onHidden(@Nullable Token token);
 
     /**
+     * Called when the user-controlled IME request was dispatched to the requesting app. The
+     * user animation can take an undetermined amount of time, so it shouldn't be tracked.
+     *
+     * @param token the token tracking the current IME request or {@code null} otherwise.
+     */
+    void onDispatched(@Nullable Token token);
+
+    /**
+     * Called when the animation of the user-controlled IME request finished.
+     *
+     * @param token the token tracking the current IME request or {@code null} otherwise.
+     * @param shown whether the end state of the animation was shown or hidden.
+     */
+    void onUserFinished(@Nullable Token token, boolean shown);
+
+    /**
      * Returns whether the current IME request was created due to a user interaction. This can
      * only be {@code true} when running on the view's UI thread.
      *
@@ -482,13 +514,6 @@
         /** Whether the stack trace at the request call site should be logged. */
         private boolean mLogStackTrace;
 
-        private void reloadSystemProperties() {
-            mLogProgress = SystemProperties.getBoolean(
-                    "persist.debug.imetracker", false);
-            mLogStackTrace = SystemProperties.getBoolean(
-                    "persist.debug.imerequest.logstacktrace", false);
-        }
-
         @NonNull
         @Override
         public Token onStart(@NonNull String component, int uid, @Type int type, @Origin int origin,
@@ -497,7 +522,7 @@
             final var token = IInputMethodManagerGlobalInvoker.onStart(tag, uid, type,
                     origin, reason, fromUser);
 
-            Log.i(TAG, token.mTag + ": onRequest" + (type == TYPE_SHOW ? "Show" : "Hide")
+            Log.i(TAG, token.mTag + ": " + getOnStartPrefix(type)
                     + " at " + Debug.originToString(origin)
                     + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason)
                     + " fromUser " + fromUser,
@@ -552,6 +577,45 @@
 
             Log.i(TAG, token.mTag + ": onHidden");
         }
+
+        @Override
+        public void onDispatched(@Nullable Token token) {
+            if (token == null) return;
+            IInputMethodManagerGlobalInvoker.onDispatched(token);
+
+            Log.i(TAG, token.mTag + ": onDispatched");
+        }
+
+        @Override
+        public void onUserFinished(@Nullable Token token, boolean shown) {
+            if (token == null) return;
+            // This is already sent to ImeTrackerService to mark it finished during onDispatched.
+
+            Log.i(TAG, token.mTag + ": onUserFinished " + (shown ? "shown" : "hidden"));
+        }
+
+        /**
+         * Gets the prefix string for {@link #onStart} based on the given request type.
+         *
+         * @param type request type for which to create the prefix string with.
+         */
+        @NonNull
+        private static String getOnStartPrefix(@Type int type) {
+            return switch (type) {
+                case TYPE_SHOW -> "onRequestShow";
+                case TYPE_HIDE -> "onRequestHide";
+                case TYPE_USER -> "onRequestUser";
+                default -> "onRequestUnknown";
+            };
+        }
+
+        /** Reloads the system properties related to this class. */
+        private void reloadSystemProperties() {
+            mLogProgress = SystemProperties.getBoolean(
+                    "persist.debug.imetracker", false);
+            mLogStackTrace = SystemProperties.getBoolean(
+                    "persist.debug.imerequest.logstacktrace", false);
+        }
     };
 
     /** The singleton IME tracker instance for instrumenting jank metrics. */
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 7885cd9..11ee286 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -54,6 +54,7 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -431,6 +432,14 @@
      * @hide
      */
     public InputMethodInfo(InputMethodInfo source) {
+        this(source, Collections.emptyList());
+    }
+
+    /**
+     * @hide
+     */
+    public InputMethodInfo(@NonNull InputMethodInfo source,
+            @NonNull List<InputMethodSubtype> additionalSubtypes) {
         mId = source.mId;
         mSettingsActivityName = source.mSettingsActivityName;
         mLanguageSettingsActivityName = source.mLanguageSettingsActivityName;
@@ -445,7 +454,19 @@
         mIsVrOnly = source.mIsVrOnly;
         mIsVirtualDeviceOnly = source.mIsVirtualDeviceOnly;
         mService = source.mService;
-        mSubtypes = source.mSubtypes;
+        if (additionalSubtypes.isEmpty()) {
+            mSubtypes = source.mSubtypes;
+        } else {
+            final ArrayList<InputMethodSubtype> subtypes = source.mSubtypes.toList();
+            final int additionalSubtypeCount = additionalSubtypes.size();
+            for (int i = 0; i < additionalSubtypeCount; ++i) {
+                final InputMethodSubtype additionalSubtype = additionalSubtypes.get(i);
+                if (!subtypes.contains(additionalSubtype)) {
+                    subtypes.add(additionalSubtype);
+                }
+            }
+            mSubtypes = new InputMethodSubtypeArray(subtypes);
+        }
         mHandledConfigChanges = source.mHandledConfigChanges;
         mSupportsStylusHandwriting = source.mSupportsStylusHandwriting;
         mSupportsConnectionlessStylusHandwriting = source.mSupportsConnectionlessStylusHandwriting;
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0e7de48..cf128fb 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2461,6 +2461,7 @@
      * @hide
      */
     public boolean hideSoftInputFromView(@NonNull View view, @HideFlags int flags) {
+        checkFocus();
         final boolean isFocusedAndWindowFocused = view.hasWindowFocus() && view.isFocused();
         synchronized (mH) {
             final boolean hasServedByInputMethod = hasServedByInputMethodLocked(view);
@@ -2529,18 +2530,6 @@
                 view, /* delegatorPackageName= */ null, /* handwritingDelegateFlags= */ 0);
     }
 
-    private void startStylusHandwritingInternalAsync(
-            @NonNull View view, @Nullable String delegatorPackageName,
-            @HandwritingDelegateFlags int handwritingDelegateFlags,
-            @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
-        Objects.requireNonNull(view);
-        Objects.requireNonNull(executor);
-        Objects.requireNonNull(callback);
-
-        startStylusHandwritingInternal(
-                view, delegatorPackageName, handwritingDelegateFlags, executor, callback);
-    }
-
     private void sendFailureCallback(@NonNull @CallbackExecutor Executor executor,
             @NonNull Consumer<Boolean> callback) {
         if (executor == null || callback == null) {
@@ -2890,7 +2879,7 @@
         if (Flags.homeScreenHandwritingDelegator()) {
             flags = delegateView.getHandwritingDelegateFlags();
         }
-        startStylusHandwritingInternalAsync(
+        acceptStylusHandwritingDelegation(
                 delegateView, delegatorPackageName, flags, executor, callback);
     }
 
@@ -2925,6 +2914,9 @@
             @HandwritingDelegateFlags int flags, @NonNull @CallbackExecutor Executor executor,
             @NonNull Consumer<Boolean> callback) {
         Objects.requireNonNull(delegatorPackageName);
+        Objects.requireNonNull(delegateView);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
 
         startStylusHandwritingInternal(
                 delegateView, delegatorPackageName, flags, executor, callback);
diff --git a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
index c243a22..e2d343f 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
@@ -25,6 +25,7 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.zip.GZIPInputStream;
 import java.util.zip.GZIPOutputStream;
@@ -163,6 +164,18 @@
     }
 
     /**
+     * @return A list of {@link InputMethodInfo} copied from this array.
+     */
+    @NonNull
+    public ArrayList<InputMethodSubtype> toList() {
+        final ArrayList<InputMethodSubtype> list = new ArrayList<>(mCount);
+        for (int i = 0; i < mCount; ++i) {
+            list.add(get(i));
+        }
+        return list;
+    }
+
+    /**
      * Return the number of {@link InputMethodSubtype} objects.
      */
     public int getCount() {
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index fa9458d..56e5bcf 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -62,6 +62,17 @@
 }
 
 flag {
+    name: "use_input_method_info_safe_list"
+    namespace: "input_method"
+    description: "Use InputMethodInfoSafeList for more reliable binder IPCs"
+    bug: "339761278"
+    is_fixed_read_only: true
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "ime_switcher_revamp"
     is_exported: true
     namespace: "input_method"
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index a07141b..b7ee0b8 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -520,6 +520,13 @@
      * To cancel the request, call <code>filePathCallback.onReceiveValue(null)</code> and
      * return {@code true}.
      *
+     * <p class="note"><b>Note:</b> WebView does not enforce any restrictions on
+     * the chosen file(s). WebView can access all files that your app can access.
+     * In case the file(s) are chosen through an untrusted source such as a third-party
+     * app, it is your own app's responsibility to check what the returned Uris
+     * refer to before calling the <code>filePathCallback</code>. See
+     * {@link #createIntent} and {@link #parseResult} for more details.</p>
+     *
      * @param webView The WebView instance that is initiating the request.
      * @param filePathCallback Invoke this callback to supply the list of paths to files to upload,
      *                         or {@code null} to cancel. Must only be called if the
@@ -556,6 +563,15 @@
          * Parse the result returned by the file picker activity. This method should be used with
          * {@link #createIntent}. Refer to {@link #createIntent} for how to use it.
          *
+         * <p class="note"><b>Note:</b> The intent returned by the file picker activity
+         * should be treated as untrusted. A third-party app handling the implicit
+         * intent created by {@link #createIntent} might return Uris that the third-party
+         * app itself does not have access to, such as your own app's sensitive data files.
+         * WebView does not enforce any restrictions on the returned Uris. It is the
+         * app's responsibility to ensure that the untrusted source (such as a third-party
+         * app) has access the Uris it has returned and that the Uris are not pointing
+         * to any sensitive data files.</p>
+         *
          * @param resultCode the integer result code returned by the file picker activity.
          * @param data the intent returned by the file picker activity.
          * @return the Uris of selected file(s) or {@code null} if the resultCode indicates
@@ -618,6 +634,12 @@
          *   WebChromeClient#onShowFileChooser}</li>
          * </ol>
          *
+         * <p class="note"><b>Note:</b> The created intent may be handled by
+         * third-party applications on device. The received result must be treated
+         * as untrusted as it can contain Uris pointing to your own app's sensitive
+         * data files. Your app should check the resultant Uris in {@link #parseResult}
+         * before calling the <code>filePathCallback</code>.</p>
+         *
          * @return an Intent that supports basic file chooser sources.
          */
         public abstract Intent createIntent();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index fb1c331..78dd3b1 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -27,6 +27,7 @@
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
+import static android.view.inputmethod.EditorInfo.STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY;
 
 import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
 import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
@@ -10062,9 +10063,22 @@
                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
                 outAttrs.setInitialSurroundingText(mText);
                 outAttrs.contentMimeTypes = getReceiveContentMimeTypes();
-                if (android.view.inputmethod.Flags.editorinfoHandwritingEnabled()
-                        && isAutoHandwritingEnabled()) {
-                    outAttrs.setStylusHandwritingEnabled(true);
+                if (android.view.inputmethod.Flags.editorinfoHandwritingEnabled()) {
+                    boolean handwritingEnabled = isAutoHandwritingEnabled();
+                    outAttrs.setStylusHandwritingEnabled(handwritingEnabled);
+                    // AndroidX Core library 1.13.0 introduced
+                    // EditorInfoCompat#setStylusHandwritingEnabled and
+                    // EditorInfoCompat#isStylusHandwritingEnabled which used a boolean value in the
+                    // EditorInfo extras bundle. These methods do not set or check the Android V
+                    // property since the Android V SDK was not yet available. In order for
+                    // EditorInfoCompat#isStylusHandwritingEnabled to return the correct value for
+                    // EditorInfo created by Android V TextView, the extras bundle value is also set
+                    // here.
+                    if (outAttrs.extras == null) {
+                        outAttrs.extras = new Bundle();
+                    }
+                    outAttrs.extras.putBoolean(
+                            STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY, handwritingEnabled);
                 }
                 ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>();
                 gestures.add(SelectGesture.class);
diff --git a/core/java/android/widget/flags/notification_widget_flags.aconfig b/core/java/android/widget/flags/notification_widget_flags.aconfig
index 3a39631..503e542 100644
--- a/core/java/android/widget/flags/notification_widget_flags.aconfig
+++ b/core/java/android/widget/flags/notification_widget_flags.aconfig
@@ -47,3 +47,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+  name: "messaging_child_request_layout"
+  namespace: "systemui"
+  description: "MessagingChild always needs to be measured during MessagingLinearLayout onMeasure."
+  bug: "324537506"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index 31e3a34..af3d7eb 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -56,6 +56,8 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.policy.DecorView;
 
+import java.io.Closeable;
+import java.io.IOException;
 import java.time.Duration;
 import java.time.Instant;
 import java.util.function.Consumer;
@@ -568,6 +570,12 @@
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         releaseAnimationSurfaceHost();
+        if (mIconView instanceof ImageView imageView
+                && imageView.getDrawable() instanceof Closeable closeableDrawable) {
+            try {
+                closeableDrawable.close();
+            } catch (IOException ignore) { }
+        }
     }
 
     @Override
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index d0f3c3e..6f7660a 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -84,8 +84,8 @@
     /**
      * Sets the activity navigation to be isolated, where the activity navigation on the
      * TaskFragment is separated from the rest activities in the Task. Activities cannot be
-     * started on an isolated TaskFragment unless the activities are launched from the same
-     * TaskFragment or explicitly requested to.
+     * started on an isolated TaskFragment unless explicitly requested to. That said, new launched
+     * activities should be positioned as a sibling to the TaskFragment with higher z-ordering.
      */
     public static final int OP_TYPE_SET_ISOLATED_NAVIGATION = 11;
 
@@ -149,6 +149,18 @@
      */
     public static final int OP_TYPE_SET_DECOR_SURFACE_BOOSTED = 18;
 
+    /**
+     * Sets the TaskFragment to be pinned.
+     * <p>
+     * If a TaskFragment is pinned, the TaskFragment should be the top-most TaskFragment among other
+     * sibling TaskFragments. Any newly launched and embeddable activity should not be placed in the
+     * pinned TaskFragment, unless the activity is launched from the pinned TaskFragment or
+     * explicitly requested to. Non-embeddable activities are not restricted to.
+     * <p>
+     * See {@link #OP_TYPE_REORDER_TO_FRONT} on how to reorder a pinned TaskFragment to the top.
+     */
+    public static final int OP_TYPE_SET_PINNED = 19;
+
     @IntDef(prefix = { "OP_TYPE_" }, value = {
             OP_TYPE_UNKNOWN,
             OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -170,6 +182,7 @@
             OP_TYPE_SET_DIM_ON_TASK,
             OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH,
             OP_TYPE_SET_DECOR_SURFACE_BOOSTED,
+            OP_TYPE_SET_PINNED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface OperationType {}
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 5c113f8..461eab6 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -18,6 +18,7 @@
 
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 
@@ -93,6 +94,19 @@
     @TaskFragmentTransitionType
     public static final int TASK_FRAGMENT_TRANSIT_CHANGE = TRANSIT_CHANGE;
 
+
+    /**
+     * The task fragment drag resize transition used by activity embedding.
+     *
+     * This value is also used in Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE and must not
+     * conflict with other predefined transition types.
+     *
+     * @hide
+     */
+    @WindowManager.TransitionType
+    @TaskFragmentTransitionType
+    public static final int TASK_FRAGMENT_TRANSIT_DRAG_RESIZE = TRANSIT_FIRST_CUSTOM + 17;
+
     /**
      * Introduced a sub set of {@link WindowManager.TransitionType} for the types that are used for
      * TaskFragment transition.
@@ -106,6 +120,7 @@
             TASK_FRAGMENT_TRANSIT_OPEN,
             TASK_FRAGMENT_TRANSIT_CLOSE,
             TASK_FRAGMENT_TRANSIT_CHANGE,
+            TASK_FRAGMENT_TRANSIT_DRAG_RESIZE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface TaskFragmentTransitionType {}
diff --git a/core/java/android/window/TaskFragmentParentInfo.java b/core/java/android/window/TaskFragmentParentInfo.java
index a77c234..1555416 100644
--- a/core/java/android/window/TaskFragmentParentInfo.java
+++ b/core/java/android/window/TaskFragmentParentInfo.java
@@ -18,6 +18,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
 import android.app.WindowConfiguration;
 import android.content.res.Configuration;
 import android.os.Parcel;
@@ -27,10 +29,13 @@
 import java.util.Objects;
 
 /**
- * The information about the parent Task of a particular TaskFragment
+ * The information about the parent Task of a particular TaskFragment.
+ *
  * @hide
  */
-public class TaskFragmentParentInfo implements Parcelable {
+@SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages.
+@TestApi
+public final class TaskFragmentParentInfo implements Parcelable {
     @NonNull
     private final Configuration mConfiguration = new Configuration();
 
@@ -42,6 +47,7 @@
 
     @Nullable private final SurfaceControl mDecorSurface;
 
+    /** @hide */
     public TaskFragmentParentInfo(@NonNull Configuration configuration, int displayId,
             boolean visible, boolean hasDirectActivity, @Nullable SurfaceControl decorSurface) {
         mConfiguration.setTo(configuration);
@@ -51,6 +57,7 @@
         mDecorSurface = decorSurface;
     }
 
+    /** @hide */
     public TaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
         mConfiguration.setTo(info.getConfiguration());
         mDisplayId = info.mDisplayId;
@@ -59,7 +66,11 @@
         mDecorSurface = info.mDecorSurface;
     }
 
-    /** The {@link Configuration} of the parent Task */
+    /**
+     * The {@link Configuration} of the parent Task
+     *
+     * @hide
+     */
     @NonNull
     public Configuration getConfiguration() {
         return mConfiguration;
@@ -68,19 +79,27 @@
     /**
      * The display ID of the parent Task. {@link android.view.Display#INVALID_DISPLAY} means the
      * Task is detached from previously associated display.
+     *
+     * @hide
      */
     public int getDisplayId() {
         return mDisplayId;
     }
 
-    /** Whether the parent Task is visible or not */
+    /**
+     * Whether the parent Task is visible or not
+     *
+     * @hide
+     */
     public boolean isVisible() {
         return mVisible;
     }
 
     /**
      * Whether the parent Task has any direct child activity, which is not embedded in any
-     * TaskFragment, or not
+     * TaskFragment, or not.
+     *
+     * @hide
      */
     public boolean hasDirectActivity() {
         return mHasDirectActivity;
@@ -93,6 +112,8 @@
      * {@link com.android.server.wm.WindowOrganizerController#configurationsAreEqualForOrganizer(
      * Configuration, Configuration)} to determine if this {@link TaskFragmentParentInfo} should
      * be dispatched to the client.
+     *
+     * @hide
      */
     public boolean equalsForTaskFragmentOrganizer(@Nullable TaskFragmentParentInfo that) {
         if (that == null) {
@@ -103,6 +124,7 @@
                 && mDecorSurface == that.mDecorSurface;
     }
 
+    /** @hide */
     @Nullable
     public SurfaceControl getDecorSurface() {
         return mDecorSurface;
@@ -156,6 +178,7 @@
         return result;
     }
 
+    @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages.
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         mConfiguration.writeToParcel(dest, flags);
@@ -173,6 +196,8 @@
         mDecorSurface = in.readTypedObject(SurfaceControl.CREATOR);
     }
 
+    @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages.
+    @NonNull
     public static final Creator<TaskFragmentParentInfo> CREATOR =
             new Creator<TaskFragmentParentInfo>() {
                 @Override
@@ -186,6 +211,7 @@
                 }
             };
 
+    @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages.
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java
index 4dada10..1e5b097 100644
--- a/core/java/android/window/TaskFragmentTransaction.java
+++ b/core/java/android/window/TaskFragmentTransaction.java
@@ -24,7 +24,6 @@
 import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.content.Intent;
-import android.content.res.Configuration;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -190,6 +189,10 @@
         @Nullable
         private IBinder mActivityToken;
 
+        /** @see #setOtherActivityToken(IBinder) */
+        @Nullable
+        private IBinder mOtherActivityToken;
+
         @Nullable
         private TaskFragmentParentInfo mTaskFragmentParentInfo;
 
@@ -211,6 +214,7 @@
             mActivityToken = in.readStrongBinder();
             mTaskFragmentParentInfo = in.readTypedObject(TaskFragmentParentInfo.CREATOR);
             mSurfaceControl = in.readTypedObject(SurfaceControl.CREATOR);
+            mOtherActivityToken = in.readStrongBinder();
         }
 
         @Override
@@ -225,6 +229,7 @@
             dest.writeStrongBinder(mActivityToken);
             dest.writeTypedObject(mTaskFragmentParentInfo, flags);
             dest.writeTypedObject(mSurfaceControl, flags);
+            dest.writeStrongBinder(mOtherActivityToken);
         }
 
         /** The change is related to the TaskFragment created with this unique token. */
@@ -248,13 +253,6 @@
             return this;
         }
 
-        // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release.
-        /** Configuration of the parent Task. */
-        @NonNull
-        public Change setTaskConfiguration(@NonNull Configuration configuration) {
-            return this;
-        }
-
         /**
          * If the {@link #TYPE_TASK_FRAGMENT_ERROR} is from a {@link WindowContainerTransaction}
          * from the {@link TaskFragmentOrganizer}, it may come with an error callback token to
@@ -299,12 +297,26 @@
             return this;
         }
 
-        // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release.
+        /**
+         * Token of another activity.
+         * <p>For {@link #TYPE_ACTIVITY_REPARENTED_TO_TASK}, it is the next activity (behind the
+         * reparented one) that fills the Task and occludes other activities. It will be the
+         * actual activity token if the activity belongs to the same process as the organizer.
+         * Otherwise, it is {@code null}.
+         *
+         * @hide
+         */
+        @NonNull
+        public Change setOtherActivityToken(@NonNull IBinder activityToken) {
+            mOtherActivityToken = requireNonNull(activityToken);
+            return this;
+        }
+
         /**
          * Sets info of the parent Task of the embedded TaskFragment.
          * @see TaskFragmentParentInfo
          *
-         * @hide pending unhide
+         * @hide
          */
         @NonNull
         public Change setTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
@@ -338,12 +350,6 @@
             return mTaskId;
         }
 
-        // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release.
-        @Nullable
-        public Configuration getTaskConfiguration() {
-            return mTaskFragmentParentInfo.getConfiguration();
-        }
-
         @Nullable
         public IBinder getErrorCallbackToken() {
             return mErrorCallbackToken;
@@ -365,8 +371,16 @@
             return mActivityToken;
         }
 
-        // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release.
-        /** @hide pending unhide */
+        /** @hide */
+        @Nullable
+        public IBinder getOtherActivityToken() {
+            return mOtherActivityToken;
+        }
+
+        /**
+         * Obtains the {@link TaskFragmentParentInfo} for this transaction.
+         */
+        @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages.
         @Nullable
         public TaskFragmentParentInfo getTaskFragmentParentInfo() {
             return mTaskFragmentParentInfo;
diff --git a/core/java/android/window/TaskSnapshot.java b/core/java/android/window/TaskSnapshot.java
index 41b6d31..a2e3d40 100644
--- a/core/java/android/window/TaskSnapshot.java
+++ b/core/java/android/window/TaskSnapshot.java
@@ -16,6 +16,7 @@
 
 package android.window;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
@@ -32,6 +33,9 @@
 import android.view.Surface;
 import android.view.WindowInsetsController;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Represents a task snapshot.
  * @hide
@@ -68,6 +72,21 @@
     private final boolean mHasImeSurface;
     // Must be one of the named color spaces, otherwise, always use SRGB color space.
     private final ColorSpace mColorSpace;
+    private int mInternalReferences;
+
+    /** This snapshot object is being broadcast. */
+    public static final int REFERENCE_BROADCAST = 1;
+    /** This snapshot object is in the cache. */
+    public static final int REFERENCE_CACHE = 1 << 1;
+    /** This snapshot object is being persistent. */
+    public static final int REFERENCE_PERSIST = 1 << 2;
+    @IntDef(flag = true, prefix = { "REFERENCE_" }, value = {
+            REFERENCE_BROADCAST,
+            REFERENCE_CACHE,
+            REFERENCE_PERSIST
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ReferenceFlags {}
 
     public TaskSnapshot(long id, long captureTime,
             @NonNull ComponentName topActivityComponent, HardwareBuffer snapshot,
@@ -296,7 +315,28 @@
                 + " mWindowingMode=" + mWindowingMode
                 + " mAppearance=" + mAppearance
                 + " mIsTranslucent=" + mIsTranslucent
-                + " mHasImeSurface=" + mHasImeSurface;
+                + " mHasImeSurface=" + mHasImeSurface
+                + " mInternalReferences=" + mInternalReferences;
+    }
+
+    /**
+     * Adds a reference when the object is held somewhere.
+     * Only used in core.
+     */
+    public synchronized void addReference(@ReferenceFlags int usage) {
+        mInternalReferences |= usage;
+    }
+
+    /**
+     * Removes a reference when the object is not held from somewhere. The snapshot will be closed
+     * once the reference becomes zero.
+     * Only used in core.
+     */
+    public synchronized void removeReference(@ReferenceFlags int usage) {
+        mInternalReferences &= ~usage;
+        if (mInternalReferences == 0 && mSnapshot != null && !mSnapshot.isClosed()) {
+            mSnapshot.close();
+        }
     }
 
     public static final @NonNull Creator<TaskSnapshot> CREATOR = new Creator<TaskSnapshot>() {
diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java
index 64fe66e..ec4e3e9 100644
--- a/core/java/android/window/TransitionFilter.java
+++ b/core/java/android/window/TransitionFilter.java
@@ -25,6 +25,7 @@
 import android.app.ActivityManager;
 import android.app.WindowConfiguration;
 import android.content.ComponentName;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.view.WindowManager;
@@ -180,6 +181,7 @@
 
         public @ContainerOrder int mOrder = CONTAINER_ORDER_ANY;
         public ComponentName mTopActivity;
+        public IBinder mLaunchCookie;
 
         public Requirement() {
         }
@@ -193,6 +195,7 @@
             mMustBeTask = in.readBoolean();
             mOrder = in.readInt();
             mTopActivity = in.readTypedObject(ComponentName.CREATOR);
+            mLaunchCookie = in.readStrongBinder();
         }
 
         /** Go through changes and find if at-least one change matches this filter */
@@ -231,6 +234,9 @@
                 if (mMustBeTask && change.getTaskInfo() == null) {
                     continue;
                 }
+                if (!matchesCookie(change.getTaskInfo())) {
+                    continue;
+                }
                 return true;
             }
             return false;
@@ -247,13 +253,25 @@
             return false;
         }
 
+        private boolean matchesCookie(ActivityManager.RunningTaskInfo info) {
+            if (mLaunchCookie == null) return true;
+            if (info == null) return false;
+            for (IBinder cookie : info.launchCookies) {
+                if (mLaunchCookie.equals(cookie)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
         /** Check if the request matches this filter. It may generate false positives */
         boolean matches(@NonNull TransitionRequestInfo request) {
             // Can't check modes/order since the transition hasn't been built at this point.
             if (mActivityType == ACTIVITY_TYPE_UNDEFINED) return true;
             return request.getTriggerTask() != null
                     && request.getTriggerTask().getActivityType() == mActivityType
-                    && matchesTopActivity(request.getTriggerTask(), null /* activityCmp */);
+                    && matchesTopActivity(request.getTriggerTask(), null /* activityCmp */)
+                    && matchesCookie(request.getTriggerTask());
         }
 
         @Override
@@ -267,6 +285,7 @@
             dest.writeBoolean(mMustBeTask);
             dest.writeInt(mOrder);
             dest.writeTypedObject(mTopActivity, flags);
+            dest.writeStrongBinder(mLaunchCookie);
         }
 
         @NonNull
@@ -307,6 +326,7 @@
             out.append(" mustBeTask=" + mMustBeTask);
             out.append(" order=" + containerOrderToString(mOrder));
             out.append(" topActivity=").append(mTopActivity);
+            out.append(" launchCookie=").append(mLaunchCookie);
             out.append("}");
             return out.toString();
         }
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 cd13c4a..4b2beb9 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
@@ -9,6 +9,16 @@
 }
 
 flag {
+  name: "disable_thin_letterboxing_policy"
+  namespace: "large_screen_experiences_app_compat"
+  description: "Whether reachability is disabled in case of thin letterboxing"
+  bug: "341027847"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "allows_screen_size_decoupled_from_status_bar_and_cutout"
   namespace: "large_screen_experiences_app_compat"
   description: "When necessary, configuration decoupled from status bar and display cutout"
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index e8b4f0b..f94766e 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -71,3 +71,17 @@
     description: "Enables quick switch for desktop mode"
     bug: "338066529"
 }
+
+flag {
+    name: "enable_additional_windows_above_status_bar"
+    namespace: "lse_desktop_experience"
+    description: "Allows for additional windows tied to WindowDecoration to be layered between status bar and notification shade."
+    bug: "316186265"
+}
+
+flag {
+    name: "enable_app_header_with_task_density"
+    namespace: "lse_desktop_experience"
+    description: "Matches the App Header density to that of the app window, instead of SysUI's"
+    bug: "332414819"
+}
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index 33af486..69cac6f 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -49,3 +49,10 @@
     description: "Prevent BAL based on it is bound by foreground Uid but the app switch is stopped."
     bug: "283801068"
 }
+
+flag {
+    name: "bal_improved_metrics"
+    namespace: "responsible_apis"
+    description: "Improved metrics."
+    bug: "339245692"
+}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 945164a..b4678f6 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -120,4 +120,14 @@
     metadata {
         purpose: PURPOSE_BUGFIX
     }
+}
+
+flag {
+    namespace: "windowing_sdk"
+    name: "fix_pip_restore_to_overlay"
+    description: "Restore exit-pip activity back to ActivityEmbedding overlay"
+    bug: "297887697"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
index 6864bf7..8e18f84 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
@@ -24,6 +24,7 @@
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SERVICE_STATUS__DISABLED;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SERVICE_STATUS__ENABLED;
@@ -32,6 +33,7 @@
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON_LONG_PRESS;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__QUICK_SETTINGS;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TRIPLE_TAP;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TWO_FINGER_TRIPLE_TAP;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
@@ -48,7 +50,6 @@
 import android.content.Context;
 import android.provider.Settings;
 
-import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
 import com.android.internal.util.FrameworkStatsLog;
 
@@ -248,6 +249,8 @@
                 }
             case HARDWARE:
                 return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY;
+            case QUICK_SETTINGS:
+                return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__QUICK_SETTINGS;
         }
         return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
     }
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index 3af1dd7..ad73294 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -166,6 +166,13 @@
     }
 
     /**
+     * Same as {@link #onPackageAdded(String, int)}, but this callback
+     * has extras passed in.
+     */
+    public void onPackageAddedWithExtras(String packageName, int uid, Bundle extras) {
+    }
+
+    /**
      * Called when a package is really removed (and not replaced).
      */
     @UnsupportedAppUsage
@@ -173,19 +180,47 @@
     }
 
     /**
+     * Same as {@link #onPackageRemoved(String, int)}, but this callback
+     * has extras passed in.
+     */
+    public void onPackageRemovedWithExtras(String packageName, int uid, Bundle extras) {
+    }
+
+    /**
      * Called when a package is really removed (and not replaced) for
      * all users on the device.
      */
     public void onPackageRemovedAllUsers(String packageName, int uid) {
     }
 
+    /**
+     * Same as {@link #onPackageRemovedAllUsers(String, int)}, but this callback
+     * has extras passed in.
+     */
+    public void onPackageRemovedAllUsersWithExtras(String packageName, int uid, Bundle extras) {
+    }
+
     public void onPackageUpdateStarted(String packageName, int uid) {
     }
 
+    /**
+     * Same as {@link #onPackageUpdateStarted(String, int)}, but this callback
+     * has extras passed in.
+     */
+    public void onPackageUpdateStartedWithExtras(String packageName, int uid, Bundle extras) {
+    }
+
     public void onPackageUpdateFinished(String packageName, int uid) {
     }
 
     /**
+     * Same as {@link #onPackageUpdateFinished(String, int)}, but this callback
+     * has extras passed in.
+     */
+    public void onPackageUpdateFinishedWithExtras(String packageName, int uid, Bundle extras) {
+    }
+
+    /**
      * Direct reflection of {@link Intent#ACTION_PACKAGE_CHANGED
      * Intent.ACTION_PACKAGE_CHANGED} being received, informing you of
      * changes to the enabled/disabled state of components in a package
@@ -283,6 +318,13 @@
     }
 
     /**
+     * Same as {@link #onPackageModified(String)}, but this callback
+     * has extras passed in.
+     */
+    public void onPackageModifiedWithExtras(@NonNull String packageName, Bundle extras) {
+    }
+
+    /**
      * Called when a package in the stopped state is started for some reason.
      *
      * @param packageName Name of the package that was unstopped
@@ -425,10 +467,13 @@
                     mModifiedPackages = mTempArray;
                     mChangeType = PACKAGE_UPDATING;
                     onPackageUpdateFinished(pkg, uid);
+                    onPackageUpdateFinishedWithExtras(pkg, uid, intent.getExtras());
                     onPackageModified(pkg);
+                    onPackageModifiedWithExtras(pkg, intent.getExtras());
                 } else {
                     mChangeType = PACKAGE_PERMANENT_CHANGE;
                     onPackageAdded(pkg, uid);
+                    onPackageAddedWithExtras(pkg, uid, intent.getExtras());
                 }
                 onPackageAppearedWithExtras(pkg, intent.getExtras());
                 onPackageAppeared(pkg, mChangeType);
@@ -442,11 +487,13 @@
                 if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
                     mChangeType = PACKAGE_UPDATING;
                     onPackageUpdateStarted(pkg, uid);
+                    onPackageUpdateStartedWithExtras(pkg, uid, intent.getExtras());
                     if (intent.getBooleanExtra(Intent.EXTRA_ARCHIVAL, false)) {
                         // In case it is a removal event due to archiving, we trigger package
                         // update event to refresh details like icons, title etc. corresponding to
                         // the archived app.
                         onPackageModified(pkg);
+                        onPackageModifiedWithExtras(pkg, intent.getExtras());
                     }
                 } else {
                     mChangeType = PACKAGE_PERMANENT_CHANGE;
@@ -455,8 +502,10 @@
                     // it when it is re-added.
                     mSomePackagesChanged = true;
                     onPackageRemoved(pkg, uid);
+                    onPackageRemovedWithExtras(pkg, uid, intent.getExtras());
                     if (intent.getBooleanExtra(Intent.EXTRA_REMOVED_FOR_ALL_USERS, false)) {
                         onPackageRemovedAllUsers(pkg, uid);
+                        onPackageRemovedAllUsersWithExtras(pkg, uid, intent.getExtras());
                     }
                 }
                 onPackageDisappearedWithExtras(pkg, intent.getExtras());
@@ -476,6 +525,7 @@
                 }
                 onPackageChangedWithExtras(pkg, intent.getExtras());
                 onPackageModified(pkg);
+                onPackageModifiedWithExtras(pkg, intent.getExtras());
             }
         } else if (Intent.ACTION_PACKAGE_DATA_CLEARED.equals(action)) {
             String pkg = getPackageName(intent);
diff --git a/core/java/com/android/internal/content/om/OverlayConfigParser.java b/core/java/com/android/internal/content/om/OverlayConfigParser.java
index faaf7d5..8132652 100644
--- a/core/java/com/android/internal/content/om/OverlayConfigParser.java
+++ b/core/java/com/android/internal/content/om/OverlayConfigParser.java
@@ -24,6 +24,8 @@
 import android.content.pm.PackagePartitions.SystemPartition;
 import android.os.Build;
 import android.os.FileUtils;
+import android.os.SystemProperties;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Xml;
@@ -241,6 +243,18 @@
         }
     }
 
+    @FunctionalInterface
+    public interface SysPropWrapper{
+        /**
+         * Get system property
+         *
+         * @param property the key to look up.
+         *
+         * @return The property value if found, empty string otherwise.
+         */
+        String get(String property);
+    }
+
     /**
      * Retrieves overlays configured within the partition in increasing priority order.
      *
@@ -320,6 +334,76 @@
     }
 
     /**
+     * Expand the property inside a rro configuration path.
+     *
+     * A RRO configuration can contain a property, this method expands
+     * the property to its value.
+     *
+     * Only read only properties allowed, prefixed with ro. Other
+     * properties will raise exception.
+     *
+     * Only a single property in the path is allowed.
+     *
+     * Example "${ro.boot.hardware.sku}/config.xml" would expand to
+     *     "G020N/config.xml"
+     *
+     * @param configPath path to expand
+     * @param sysPropWrapper method used for reading properties
+     *
+     * @return The expanded path. Returns null if configPath is null.
+     */
+    @VisibleForTesting
+    public static String expandProperty(String configPath,
+            SysPropWrapper sysPropWrapper) {
+        if (configPath == null) {
+            return null;
+        }
+
+        int propStartPos = configPath.indexOf("${");
+        if (propStartPos == -1) {
+            // No properties inside the string, return as is
+            return configPath;
+        }
+
+        final StringBuilder sb = new StringBuilder();
+        sb.append(configPath.substring(0, propStartPos));
+
+        // Read out the end position
+        int propEndPos = configPath.indexOf("}", propStartPos);
+        if (propEndPos == -1) {
+            throw new IllegalStateException("Malformed property, unmatched braces, in: "
+                    + configPath);
+        }
+
+        // Confirm that there is only one property inside the string
+        if (configPath.indexOf("${", propStartPos + 2) != -1) {
+            throw new IllegalStateException("Only a single property supported in path: "
+                    + configPath);
+        }
+
+        final String propertyName = configPath.substring(propStartPos + 2, propEndPos);
+        if (!propertyName.startsWith("ro.")) {
+            throw new IllegalStateException("Only read only properties can be used when "
+                    + "merging RRO config files: " + propertyName);
+        }
+        final String propertyValue = sysPropWrapper.get(propertyName);
+        if (TextUtils.isEmpty(propertyValue)) {
+            throw new IllegalStateException("Property is empty or doesn't exist: " + propertyName);
+        }
+        Log.d(TAG, String.format("Using property in overlay config path: \"%s\"", propertyName));
+        sb.append(propertyValue);
+
+        // propEndPos points to '}', need to step to next character, might be outside of string
+        propEndPos = propEndPos + 1;
+        // Append the remainder, if exists
+        if (propEndPos < configPath.length()) {
+            sb.append(configPath.substring(propEndPos));
+        }
+
+        return sb.toString();
+    }
+
+    /**
      * Parses a <merge> tag within an overlay configuration file.
      *
      * Merge tags allow for other configuration files to be "merged" at the current parsing
@@ -331,10 +415,21 @@
             @Nullable OverlayScanner scanner,
             @Nullable Map<String, ParsedOverlayInfo> packageManagerOverlayInfos,
             @NonNull ParsingContext parsingContext) {
-        final String path = parser.getAttributeValue(null, "path");
+        final String path;
+
+        try {
+            SysPropWrapper sysPropWrapper = p -> {
+                return SystemProperties.get(p, "");
+            };
+            path = expandProperty(parser.getAttributeValue(null, "path"), sysPropWrapper);
+        } catch (IllegalStateException e) {
+            throw new IllegalStateException(String.format("<merge> path expand error in %s at %s",
+                    configFile, parser.getPositionDescription()), e);
+        }
+
         if (path == null) {
-            throw new IllegalStateException(String.format("<merge> without path in %s at %s"
-                    + configFile, parser.getPositionDescription()));
+            throw new IllegalStateException(String.format("<merge> without path in %s at %s",
+                    configFile, parser.getPositionDescription()));
         }
 
         if (path.startsWith("/")) {
diff --git a/core/java/com/android/internal/inputmethod/IImeTracker.aidl b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
index ab4edb6..ebae39e 100644
--- a/core/java/com/android/internal/inputmethod/IImeTracker.aidl
+++ b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
@@ -64,20 +64,28 @@
     oneway void onCancelled(in ImeTracker.Token statsToken, int phase);
 
     /**
-     * Called when the IME show request is successful.
+     * Called when the show IME request is successful.
      *
      * @param statsToken the token tracking the current IME request.
      */
     oneway void onShown(in ImeTracker.Token statsToken);
 
     /**
-     * Called when the IME hide request is successful.
+     * Called when the hide IME request is successful.
      *
      * @param statsToken the token tracking the current IME request.
      */
     oneway void onHidden(in ImeTracker.Token statsToken);
 
     /**
+     * Called when the user-controlled IME request was dispatched to the requesting app. The
+     * user animation can take an undetermined amount of time, so it shouldn't be tracked.
+     *
+     * @param statsToken the token tracking the current IME request.
+     */
+    oneway void onDispatched(in ImeTracker.Token statsToken);
+
+    /**
      * Checks whether there are any pending IME visibility requests.
      *
      * @return {@code true} iff there are pending IME visibility requests.
diff --git a/core/java/com/android/internal/inputmethod/ImeTracing.java b/core/java/com/android/internal/inputmethod/ImeTracing.java
index ee9c3aa..cd4ccda 100644
--- a/core/java/com/android/internal/inputmethod/ImeTracing.java
+++ b/core/java/com/android/internal/inputmethod/ImeTracing.java
@@ -60,7 +60,9 @@
      */
     public static ImeTracing getInstance() {
         if (sInstance == null) {
-            if (isSystemProcess()) {
+            if (android.tracing.Flags.perfettoIme()) {
+                sInstance = new ImeTracingPerfettoImpl();
+            } else if (isSystemProcess()) {
                 sInstance = new ImeTracingServerImpl();
             } else {
                 sInstance = new ImeTracingClientImpl();
@@ -78,7 +80,7 @@
      * and {@see #IME_TRACING_FROM_IMS}
      * @param where
      */
-    public void sendToService(byte[] protoDump, int source, String where) {
+    protected void sendToService(byte[] protoDump, int source, String where) {
         InputMethodManagerGlobal.startProtoDump(protoDump, source, where,
                 e -> Log.e(TAG, "Exception while sending ime-related dump to server", e));
     }
diff --git a/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java b/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java
new file mode 100644
index 0000000..24cd1c9
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+import static android.tracing.perfetto.DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.internal.perfetto.protos.Inputmethodeditor.InputMethodClientsTraceProto;
+import android.internal.perfetto.protos.Inputmethodeditor.InputMethodManagerServiceTraceProto;
+import android.internal.perfetto.protos.Inputmethodeditor.InputMethodServiceTraceProto;
+import android.internal.perfetto.protos.TracePacketOuterClass.TracePacket;
+import android.internal.perfetto.protos.WinscopeExtensionsImplOuterClass.WinscopeExtensionsImpl;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.tracing.inputmethod.InputMethodDataSource;
+import android.tracing.perfetto.DataSourceParams;
+import android.tracing.perfetto.InitArguments;
+import android.tracing.perfetto.Producer;
+import android.util.proto.ProtoOutputStream;
+import android.view.inputmethod.InputMethodManager;
+
+import java.io.PrintWriter;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * An implementation of {@link ImeTracing} for perfetto tracing.
+ */
+final class ImeTracingPerfettoImpl extends ImeTracing {
+    private final AtomicInteger mTracingSessionsCount = new AtomicInteger(0);
+    private final AtomicBoolean mIsClientDumpInProgress = new AtomicBoolean(false);
+    private final AtomicBoolean mIsServiceDumpInProgress = new AtomicBoolean(false);
+    private final AtomicBoolean mIsManagerServiceDumpInProgress = new AtomicBoolean(false);
+    private final InputMethodDataSource mDataSource = new InputMethodDataSource(
+            mTracingSessionsCount::incrementAndGet,
+            mTracingSessionsCount::decrementAndGet);
+
+    ImeTracingPerfettoImpl() {
+        Producer.init(InitArguments.DEFAULTS);
+        DataSourceParams params =
+                new DataSourceParams.Builder()
+                        .setBufferExhaustedPolicy(
+                                PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)
+                        .setNoFlush(true)
+                        .setWillNotifyOnStop(false)
+                        .build();
+        mDataSource.register(params);
+    }
+
+
+    @Override
+    public void triggerClientDump(String where, InputMethodManager immInstance,
+            @Nullable byte[] icProto) {
+        if (!isEnabled() || !isAvailable()) {
+            return;
+        }
+
+        if (!mIsClientDumpInProgress.compareAndSet(false, true)) {
+            return;
+        }
+
+        if (immInstance == null) {
+            return;
+        }
+
+        try {
+            Trace.beginSection("inputmethod_client_dump");
+            mDataSource.trace((ctx) -> {
+                final ProtoOutputStream os = ctx.newTracePacket();
+                os.write(TracePacket.TIMESTAMP, SystemClock.elapsedRealtimeNanos());
+                final long tokenWinscopeExtensions =
+                        os.start(TracePacket.WINSCOPE_EXTENSIONS);
+                final long tokenExtensionsField =
+                        os.start(WinscopeExtensionsImpl.INPUTMETHOD_CLIENTS);
+                os.write(InputMethodClientsTraceProto.WHERE, where);
+                final long tokenClient =
+                        os.start(InputMethodClientsTraceProto.CLIENT);
+                immInstance.dumpDebug(os, icProto);
+                os.end(tokenClient);
+                os.end(tokenExtensionsField);
+                os.end(tokenWinscopeExtensions);
+            });
+        } finally {
+            mIsClientDumpInProgress.set(false);
+            Trace.endSection();
+        }
+    }
+
+    @Override
+    public void triggerServiceDump(String where,
+            @NonNull ServiceDumper dumper, @Nullable byte[] icProto) {
+        if (!isEnabled() || !isAvailable()) {
+            return;
+        }
+
+        if (!mIsServiceDumpInProgress.compareAndSet(false, true)) {
+            return;
+        }
+
+        try {
+            Trace.beginSection("inputmethod_service_dump");
+            mDataSource.trace((ctx) -> {
+                final ProtoOutputStream os = ctx.newTracePacket();
+                os.write(TracePacket.TIMESTAMP, SystemClock.elapsedRealtimeNanos());
+                final long tokenWinscopeExtensions =
+                        os.start(TracePacket.WINSCOPE_EXTENSIONS);
+                final long tokenExtensionsField =
+                        os.start(WinscopeExtensionsImpl.INPUTMETHOD_SERVICE);
+                os.write(InputMethodServiceTraceProto.WHERE, where);
+                dumper.dumpToProto(os, icProto);
+                os.end(tokenExtensionsField);
+                os.end(tokenWinscopeExtensions);
+            });
+        } finally {
+            mIsServiceDumpInProgress.set(false);
+            Trace.endSection();
+        }
+    }
+
+    @Override
+    public void triggerManagerServiceDump(@NonNull String where, @NonNull ServiceDumper dumper) {
+        if (!isEnabled() || !isAvailable()) {
+            return;
+        }
+
+        if (!mIsManagerServiceDumpInProgress.compareAndSet(false, true)) {
+            return;
+        }
+
+        try {
+            Trace.beginSection("inputmethod_manager_service_dump");
+            mDataSource.trace((ctx) -> {
+                final ProtoOutputStream os = ctx.newTracePacket();
+                os.write(TracePacket.TIMESTAMP, SystemClock.elapsedRealtimeNanos());
+                final long tokenWinscopeExtensions =
+                        os.start(TracePacket.WINSCOPE_EXTENSIONS);
+                final long tokenExtensionsField =
+                        os.start(WinscopeExtensionsImpl.INPUTMETHOD_MANAGER_SERVICE);
+                os.write(InputMethodManagerServiceTraceProto.WHERE, where);
+                dumper.dumpToProto(os, null);
+                os.end(tokenExtensionsField);
+                os.end(tokenWinscopeExtensions);
+            });
+        } finally {
+            mIsManagerServiceDumpInProgress.set(false);
+            Trace.endSection();
+        }
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return mTracingSessionsCount.get() > 0;
+    }
+
+    @Override
+    public void startTrace(@Nullable PrintWriter pw) {
+        // Intentionally left empty. Tracing start/stop is managed through Perfetto.
+    }
+
+    @Override
+    public void stopTrace(@Nullable PrintWriter pw) {
+        // Intentionally left empty. Tracing start/stop is managed through Perfetto.
+    }
+
+    @Override
+    public void addToBuffer(ProtoOutputStream proto, int source) {
+        // Intentionally left empty. Only used for legacy tracing.
+    }
+}
diff --git a/core/java/com/android/internal/inputmethod/InlineSuggestionsRequestCallback.java b/core/java/com/android/internal/inputmethod/InlineSuggestionsRequestCallback.java
new file mode 100644
index 0000000..92d453b
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/InlineSuggestionsRequestCallback.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+import android.annotation.BinderThread;
+import android.view.autofill.AutofillId;
+import android.view.inputmethod.InlineSuggestionsRequest;
+
+/**
+ * An internal interface that mirrors {@link IInlineSuggestionsRequestCallback}.
+ *
+ * <p>This interface is used to forward incoming IPCs from
+ * {@link com.android.server.inputmethod.AutofillSuggestionsController} to
+ * {@link com.android.server.autofill.AutofillInlineSuggestionsRequestSession}.</p>
+ */
+public interface InlineSuggestionsRequestCallback {
+
+    /**
+     * Forwards {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsUnsupported()}.
+     */
+    @BinderThread
+    void onInlineSuggestionsUnsupported();
+
+    /**
+     * Forwards {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsRequest(
+     * InlineSuggestionsRequest, IInlineSuggestionsResponseCallback)}.
+     */
+    @BinderThread
+    void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
+            IInlineSuggestionsResponseCallback callback);
+
+    /**
+     * Forwards {@link IInlineSuggestionsRequestCallback#onInputMethodStartInput(AutofillId)}.
+     */
+    @BinderThread
+    void onInputMethodStartInput(AutofillId imeFieldId);
+
+    /**
+     * Forwards {@link IInlineSuggestionsRequestCallback#onInputMethodShowInputRequested(boolean)}.
+     */
+    @BinderThread
+    void onInputMethodShowInputRequested(boolean requestResult);
+
+    /**
+     * Forwards {@link IInlineSuggestionsRequestCallback#onInputMethodStartInputView()}.
+     */
+    @BinderThread
+    void onInputMethodStartInputView();
+
+    /**
+     * Forwards {@link IInlineSuggestionsRequestCallback#onInputMethodFinishInputView()}.
+     */
+    @BinderThread
+    void onInputMethodFinishInputView();
+
+    /**
+     * Forwards {@link IInlineSuggestionsRequestCallback#onInputMethodFinishInput()}.
+     */
+    @BinderThread
+    void onInputMethodFinishInput();
+
+    /**
+     * Forwards {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsSessionInvalidated()}.
+     */
+    @BinderThread
+    void onInlineSuggestionsSessionInvalidated();
+}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
index a0aad31..2a5593f 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
@@ -297,6 +297,8 @@
                 return "SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT";
             case SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION:
                 return "SHOW_SOFT_INPUT_IMM_DEPRECATION";
+            case SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION:
+                return "CONTROL_WINDOW_INSETS_ANIMATION";
             default:
                 return "Unknown=" + reason;
         }
diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
index da738a0..eb6a810 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -88,6 +88,7 @@
         SoftInputShowHideReason.HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL,
         SoftInputShowHideReason.SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT,
         SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION,
+        SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION,
 })
 public @interface SoftInputShowHideReason {
     /** Default, undefined reason. */
@@ -397,4 +398,10 @@
      * {@link InputMethodManager#showSoftInputFromInputMethod(IBinder, int)}.
      */
     int SHOW_SOFT_INPUT_IMM_DEPRECATION = ImeProtoEnums.REASON_SHOW_SOFT_INPUT_IMM_DEPRECATION;
+
+    /**
+     * Show / Hide soft input by application-controlled animation in
+     * {@link android.view.InsetsController#controlWindowInsetsAnimation}.
+     */
+    int CONTROL_WINDOW_INSETS_ANIMATION = ImeProtoEnums.REASON_CONTROL_WINDOW_INSETS_ANIMATION;
 }
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index f2d2c1b..6ffa826 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -134,10 +134,12 @@
     public static final int CUJ_LAUNCHER_WIDGET_PICKER_SEARCH_BACK = 99;
     public static final int CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK = 100;
     public static final int CUJ_LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK = 101;
+    public static final int CUJ_LAUNCHER_PRIVATE_SPACE_LOCK = 102;
+    public static final int CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK = 103;
 
     // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
     @VisibleForTesting
-    static final int LAST_CUJ = CUJ_LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK;
+    static final int LAST_CUJ = CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK;
 
     /** @hide */
     @IntDef({
@@ -230,7 +232,9 @@
             CUJ_LAUNCHER_TASKBAR_ALL_APPS_SEARCH_BACK,
             CUJ_LAUNCHER_WIDGET_PICKER_CLOSE_BACK,
             CUJ_LAUNCHER_WIDGET_PICKER_SEARCH_BACK,
-            CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK
+            CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK,
+            CUJ_LAUNCHER_PRIVATE_SPACE_LOCK,
+            CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
@@ -335,6 +339,8 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_WIDGET_PICKER_SEARCH_BACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_WIDGET_PICKER_SEARCH_BACK;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_PRIVATE_SPACE_LOCK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_PRIVATE_SPACE_LOCK;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_PRIVATE_SPACE_UNLOCK;
     }
 
     private Cuj() {
@@ -533,6 +539,10 @@
                 return "LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK";
             case CUJ_LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK:
                 return "LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK";
+            case CUJ_LAUNCHER_PRIVATE_SPACE_LOCK:
+                return "LAUNCHER_PRIVATE_SPACE_LOCK";
+            case CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK:
+                return "LAUNCHER_PRIVATE_SPACE_UNLOCK";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 9f9aae5..24971f5 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.BatteryConsumer;
+import android.os.BatteryStats;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.PersistableBundle;
@@ -34,8 +35,12 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Container for power stats, acquired by various PowerStatsCollector classes. See subclasses for
@@ -75,6 +80,10 @@
      */
     @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static class Descriptor {
+        public static final String EXTRA_DEVICE_STATS_FORMAT = "format-device";
+        public static final String EXTRA_STATE_STATS_FORMAT = "format-state";
+        public static final String EXTRA_UID_STATS_FORMAT = "format-uid";
+
         public static final String XML_TAG_DESCRIPTOR = "descriptor";
         private static final String XML_ATTR_ID = "id";
         private static final String XML_ATTR_NAME = "name";
@@ -120,6 +129,10 @@
          */
         public final PersistableBundle extras;
 
+        private PowerStatsFormatter mDeviceStatsFormatter;
+        private PowerStatsFormatter mStateStatsFormatter;
+        private PowerStatsFormatter mUidStatsFormatter;
+
         public Descriptor(@BatteryConsumer.PowerComponent int powerComponentId,
                 int statsArrayLength, @Nullable SparseArray<String> stateLabels,
                 int stateStatsArrayLength, int uidStatsArrayLength,
@@ -131,7 +144,7 @@
 
         public Descriptor(int customPowerComponentId, String name, int statsArrayLength,
                 @Nullable SparseArray<String> stateLabels, int stateStatsArrayLength,
-                int uidStatsArrayLength, PersistableBundle extras) {
+                int uidStatsArrayLength, @NonNull PersistableBundle extras) {
             if (statsArrayLength > MAX_STATS_ARRAY_LENGTH) {
                 throw new IllegalArgumentException(
                         "statsArrayLength is too high. Max = " + MAX_STATS_ARRAY_LENGTH);
@@ -154,6 +167,39 @@
         }
 
         /**
+         * Returns a custom formatter for this type of power stats.
+         */
+        public PowerStatsFormatter getDeviceStatsFormatter() {
+            if (mDeviceStatsFormatter == null) {
+                mDeviceStatsFormatter = new PowerStatsFormatter(
+                        extras.getString(EXTRA_DEVICE_STATS_FORMAT));
+            }
+            return mDeviceStatsFormatter;
+        }
+
+        /**
+         * Returns a custom formatter for this type of power stats, specifically per-state stats.
+         */
+        public PowerStatsFormatter getStateStatsFormatter() {
+            if (mStateStatsFormatter == null) {
+                mStateStatsFormatter = new PowerStatsFormatter(
+                        extras.getString(EXTRA_STATE_STATS_FORMAT));
+            }
+            return mStateStatsFormatter;
+        }
+
+        /**
+         * Returns a custom formatter for this type of power stats, specifically per-UID stats.
+         */
+        public PowerStatsFormatter getUidStatsFormatter() {
+            if (mUidStatsFormatter == null) {
+                mUidStatsFormatter = new PowerStatsFormatter(
+                        extras.getString(EXTRA_UID_STATS_FORMAT));
+            }
+            return mUidStatsFormatter;
+        }
+
+        /**
          * Returns the label associated with the give state key, e.g. "5G-high" for the
          * state of Mobile Radio representing the 5G mode and high signal power.
          */
@@ -491,20 +537,22 @@
         StringBuilder sb = new StringBuilder();
         sb.append("duration=").append(durationMs).append(" ").append(descriptor.name);
         if (stats.length > 0) {
-            sb.append("=").append(Arrays.toString(stats));
+            sb.append("=").append(descriptor.getDeviceStatsFormatter().format(stats));
         }
         if (descriptor.stateStatsArrayLength != 0) {
+            PowerStatsFormatter formatter = descriptor.getStateStatsFormatter();
             for (int i = 0; i < stateStats.size(); i++) {
-                sb.append(" [");
+                sb.append(" (");
                 sb.append(descriptor.getStateLabel(stateStats.keyAt(i)));
-                sb.append("]=");
-                sb.append(Arrays.toString(stateStats.valueAt(i)));
+                sb.append(") ");
+                sb.append(formatter.format(stateStats.valueAt(i)));
             }
         }
+        PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter();
         for (int i = 0; i < uidStats.size(); i++) {
             sb.append(uidPrefix)
                     .append(UserHandle.formatUid(uidStats.keyAt(i)))
-                    .append(": ").append(Arrays.toString(uidStats.valueAt(i)));
+                    .append(": ").append(uidStatsFormatter.format(uidStats.valueAt(i)));
         }
         return sb.toString();
     }
@@ -513,26 +561,29 @@
      * Prints the contents of the stats snapshot.
      */
     public void dump(IndentingPrintWriter pw) {
-        pw.println("PowerStats: " + descriptor.name + " (" + descriptor.powerComponentId + ')');
+        pw.println(descriptor.name + " (" + descriptor.powerComponentId + ')');
         pw.increaseIndent();
         pw.print("duration", durationMs).println();
+
         if (descriptor.statsArrayLength != 0) {
-            pw.print("stats", Arrays.toString(stats)).println();
+            pw.println(descriptor.getDeviceStatsFormatter().format(stats));
         }
         if (descriptor.stateStatsArrayLength != 0) {
+            PowerStatsFormatter formatter = descriptor.getStateStatsFormatter();
             for (int i = 0; i < stateStats.size(); i++) {
-                pw.print("state ");
+                pw.print(" (");
                 pw.print(descriptor.getStateLabel(stateStats.keyAt(i)));
-                pw.print(": ");
-                pw.print(Arrays.toString(stateStats.valueAt(i)));
+                pw.print(") ");
+                pw.print(formatter.format(stateStats.valueAt(i)));
                 pw.println();
             }
         }
+        PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter();
         for (int i = 0; i < uidStats.size(); i++) {
             pw.print("UID ");
-            pw.print(uidStats.keyAt(i));
+            pw.print(UserHandle.formatUid(uidStats.keyAt(i)));
             pw.print(": ");
-            pw.print(Arrays.toString(uidStats.valueAt(i)));
+            pw.print(uidStatsFormatter.format(uidStats.valueAt(i)));
             pw.println();
         }
         pw.decreaseIndent();
@@ -542,4 +593,126 @@
     public String toString() {
         return "PowerStats: " + formatForBatteryHistory(" UID ");
     }
+
+    public static class PowerStatsFormatter {
+        private static class Section {
+            public String label;
+            public int position;
+            public int length;
+            public boolean optional;
+            public boolean typePower;
+        }
+
+        private static final double NANO_TO_MILLI_MULTIPLIER = 1.0 / 1000000.0;
+        private static final Pattern SECTION_PATTERN =
+                Pattern.compile("([^:]+):(\\d+)(\\[(?<L>\\d+)])?(?<F>\\S*)\\s*");
+        private final List<Section> mSections;
+
+        public PowerStatsFormatter(String format) {
+            mSections = parseFormat(format);
+        }
+
+        /**
+         * Produces a formatted string representing the supplied array, with labels
+         * and other adornments specific to the power stats layout.
+         */
+        public String format(long[] stats) {
+            return format(mSections, stats);
+        }
+
+        private List<Section> parseFormat(String format) {
+            if (format == null || format.isBlank()) {
+                return null;
+            }
+
+            ArrayList<Section> sections = new ArrayList<>();
+            Matcher matcher = SECTION_PATTERN.matcher(format);
+            for (int position = 0; position < format.length(); position = matcher.end()) {
+                if (!matcher.find() || matcher.start() != position) {
+                    Slog.wtf(TAG, "Bad power stats format '" + format + "'");
+                    return null;
+                }
+                Section section = new Section();
+                section.label = matcher.group(1);
+                section.position = Integer.parseUnsignedInt(matcher.group(2));
+                String length = matcher.group("L");
+                if (length != null) {
+                    section.length = Integer.parseUnsignedInt(length);
+                } else {
+                    section.length = 1;
+                }
+                String flags = matcher.group("F");
+                if (flags != null) {
+                    for (int i = 0; i < flags.length(); i++) {
+                        char flag = flags.charAt(i);
+                        switch (flag) {
+                            case '?':
+                                section.optional = true;
+                                break;
+                            case 'p':
+                                section.typePower = true;
+                                break;
+                            default:
+                                Slog.e(TAG,
+                                        "Unsupported format option '" + flag + "' in " + format);
+                                break;
+                        }
+                    }
+                }
+                sections.add(section);
+            }
+
+            return sections;
+        }
+
+        private String format(List<Section> sections, long[] stats) {
+            if (sections == null) {
+                return Arrays.toString(stats);
+            }
+
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0, count = sections.size(); i < count; i++) {
+                Section section = sections.get(i);
+                if (section.length == 0) {
+                    continue;
+                }
+
+                if (section.optional) {
+                    boolean nonZero = false;
+                    for (int offset = 0; offset < section.length; offset++) {
+                        if (stats[section.position + offset] != 0) {
+                            nonZero = true;
+                            break;
+                        }
+                    }
+                    if (!nonZero) {
+                        continue;
+                    }
+                }
+
+                if (!sb.isEmpty()) {
+                    sb.append(' ');
+                }
+                sb.append(section.label).append(": ");
+                if (section.length != 1) {
+                    sb.append('[');
+                }
+                for (int offset = 0; offset < section.length; offset++) {
+                    if (offset != 0) {
+                        sb.append(", ");
+                    }
+                    if (section.typePower) {
+                        sb.append(BatteryStats.formatCharge(
+                                stats[section.position + offset] * NANO_TO_MILLI_MULTIPLIER));
+                    } else {
+                        sb.append(stats[section.position + offset]);
+                    }
+                }
+                if (section.length != 1) {
+                    sb.append(']');
+                }
+            }
+            return sb.toString();
+        }
+    }
 }
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 97ce96e..1dcd893 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -1908,12 +1908,16 @@
             } else if (parser.getName().equals("package")) {
                 final TypedArray sa = res.obtainAttributes(parser,
                         R.styleable.AndroidManifestQueriesPackage);
-                final String packageName = sa.getNonConfigurationString(
-                        R.styleable.AndroidManifestQueriesPackage_name, 0);
-                if (TextUtils.isEmpty(packageName)) {
-                    return input.error("Package name is missing from package tag.");
+                try {
+                    final String packageName = sa.getNonConfigurationString(
+                            R.styleable.AndroidManifestQueriesPackage_name, 0);
+                    if (TextUtils.isEmpty(packageName)) {
+                        return input.error("Package name is missing from package tag.");
+                    }
+                    pkg.addQueriesPackage(packageName.intern());
+                } finally {
+                    sa.recycle();
                 }
-                pkg.addQueriesPackage(packageName.intern());
             } else if (parser.getName().equals("provider")) {
                 final TypedArray sa = res.obtainAttributes(parser,
                         R.styleable.AndroidManifestQueriesProvider);
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index ee33eb4..37b7288 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -131,8 +131,13 @@
             Runnable cacheUpdater
     ) {
         Producer.init(InitArguments.DEFAULTS);
-        mDataSource.register(new DataSourceParams(
-                DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT));
+        DataSourceParams params =
+                new DataSourceParams.Builder()
+                        .setBufferExhaustedPolicy(
+                                DataSourceParams
+                                        .PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)
+                        .build();
+        mDataSource.register(params);
         this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
         this.mViewerConfigReader = viewerConfigReader;
         this.mLogGroups = logGroups;
@@ -186,8 +191,6 @@
                 }
 
                 os.end(outProtologViewerConfigToken);
-
-                ctx.flush();
             } catch (IOException e) {
                 Log.e(LOG_TAG, "Failed to read ProtoLog viewer config to dump on tracing end", e);
             }
diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
new file mode 100644
index 0000000..1340156
--- /dev/null
+++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.ravenwood;
+
+/**
+ * Class to interact with the Ravenwood environment.
+ */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+public class RavenwoodEnvironment {
+    private static RavenwoodEnvironment sInstance = new RavenwoodEnvironment();
+
+    private RavenwoodEnvironment() {
+    }
+
+    /**
+     * @return the singleton instance.
+     */
+    public static RavenwoodEnvironment getInstance() {
+        return sInstance;
+    }
+
+    /**
+     * USE IT SPARINGLY! Returns true if it's running on Ravenwood, hostside test environment.
+     *
+     * <p>Using this allows code to behave differently on a real device and on Ravenwood, but
+     * generally speaking, that's a bad idea because we want the test target code to behave
+     * differently.
+     *
+     * <p>This should be only used when different behavior is absolutely needed.
+     *
+     * <p>If someone needs it without having access to the SDK, the following hack would work too.
+     * <code>System.getProperty("java.class.path").contains("ravenwood")</code>
+     */
+    @android.ravenwood.annotation.RavenwoodReplace
+    public boolean isRunningOnRavenwood() {
+        return false;
+    }
+
+    public boolean isRunningOnRavenwood$ravenwood() {
+        return true;
+    }
+}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index f931a76..e29f256 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -47,7 +47,7 @@
     void animateExpandNotificationsPanel();
     void animateExpandSettingsPanel(String subPanel);
     void animateCollapsePanels();
-    void togglePanel();
+    void toggleNotificationsPanel();
 
     void showWirelessChargingAnimation(int batteryLevel);
 
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index b83b2d2..fc60f06 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -17,7 +17,6 @@
 package com.android.internal.statusbar;
 
 import android.app.Notification;
-import android.app.StatusBarManager;
 import android.content.ComponentName;
 import android.graphics.drawable.Icon;
 import android.graphics.Rect;
@@ -53,9 +52,9 @@
     void togglePanel();
     @UnsupportedAppUsage
     void disable(int what, IBinder token, String pkg);
+    void disableForUser(int what, IBinder token, String pkg, int userId);
     void disable2(int what, IBinder token, String pkg);
-    void disableForUser(in StatusBarManager.DisableInfo info, IBinder token, String pkg, int userId, String reason);
-
+    void disable2ForUser(int what, IBinder token, String pkg, int userId);
     int[] getDisableFlags(IBinder token, int userId);
     void setIcon(String slot, String iconPackage, int iconId, int iconLevel, String contentDescription);
     @UnsupportedAppUsage
diff --git a/core/java/com/android/internal/util/NewlineNormalizer.java b/core/java/com/android/internal/util/NewlineNormalizer.java
new file mode 100644
index 0000000..0104d1f
--- /dev/null
+++ b/core/java/com/android/internal/util/NewlineNormalizer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+
+import java.util.regex.Pattern;
+
+/**
+ * Utility class that replaces consecutive empty lines with single new line.
+ * @hide
+ */
+public class NewlineNormalizer {
+
+    private static final Pattern MULTIPLE_NEWLINES = Pattern.compile("\\v(\\s*\\v)?");
+
+    // Private constructor to prevent instantiation
+    private NewlineNormalizer() {}
+
+    /**
+     * Replaces consecutive newlines with a single newline in the input text.
+     */
+    public static String normalizeNewlines(String text) {
+        return MULTIPLE_NEWLINES.matcher(text).replaceAll("\n");
+    }
+}
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index e33704b..3fc4fff 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -18,7 +18,6 @@
 
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.hardware.input.InputManagerGlobal;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -29,7 +28,6 @@
 import android.view.IWindowSession;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
-import android.view.PointerIcon;
 import android.view.ScrollCaptureResponse;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.inputmethod.ImeTracker;
@@ -128,12 +126,6 @@
     }
 
     @Override
-    public void updatePointerIcon(float x, float y) {
-        InputManagerGlobal.getInstance()
-                .setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED);
-    }
-
-    @Override
     public void dispatchWallpaperCommand(String action, int x, int y,
             int z, Bundle extras, boolean sync) {
         if (sync) {
@@ -162,4 +154,9 @@
             // ignore
         }
     }
+
+    @Override
+    public void dumpWindow(ParcelFileDescriptor pfd) {
+
+    }
 }
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 66b0158..0734e68 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -886,9 +886,16 @@
             cellState.activationAnimator.cancel();
         }
         AnimatorSet animatorSet = new AnimatorSet();
+
+        // When running the line end animation (see doc for createLineEndAnimation), if cell is in:
+        // - activate state - use finger position at the time of hit detection
+        // - deactivate state - use current position where the end was last during initial animation
+        // Note that deactivate state will only come if mKeepDotActivated is themed true.
+        final float startX = activate == CELL_ACTIVATE ? mInProgressX : cellState.lineEndX;
+        final float startY = activate == CELL_ACTIVATE ? mInProgressY : cellState.lineEndY;
         AnimatorSet.Builder animatorSetBuilder = animatorSet
                 .play(createLineDisappearingAnimation())
-                .with(createLineEndAnimation(cellState, mInProgressX, mInProgressY,
+                .with(createLineEndAnimation(cellState, startX, startY,
                         getCenterXForColumn(cell.column), getCenterYForRow(cell.row)));
         if (mDotSize != mDotSizeActivated) {
             animatorSetBuilder.with(createDotRadiusAnimation(cellState));
diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java
index e07acac..3d8237e 100644
--- a/core/java/com/android/internal/widget/MessagingLinearLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.widget;
 
+import static android.widget.flags.Flags.messagingChildRequestLayout;
+
 import android.annotation.Nullable;
 import android.annotation.Px;
 import android.content.Context;
@@ -92,6 +94,10 @@
             final View child = getChildAt(i);
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
             lp.hide = true;
+            // Child always needs to be measured to calculate hide property correctly in onMeasure.
+            if (messagingChildRequestLayout()) {
+                child.requestLayout();
+            }
             if (child instanceof MessagingChild) {
                 MessagingChild messagingChild = (MessagingChild) child;
                 // Whenever we encounter the message first, it's always first in the layout
diff --git a/core/java/com/android/internal/widget/NotificationRowIconView.java b/core/java/com/android/internal/widget/NotificationRowIconView.java
index 4031b2f..0f4615a 100644
--- a/core/java/com/android/internal/widget/NotificationRowIconView.java
+++ b/core/java/com/android/internal/widget/NotificationRowIconView.java
@@ -16,18 +16,27 @@
 
 package com.android.internal.widget;
 
+import android.annotation.Nullable;
 import android.app.Flags;
 import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.util.AttributeSet;
+import android.view.RemotableViewMethod;
 import android.widget.RemoteViews;
 
-import androidx.annotation.Nullable;
-
 /**
  * An image view that holds the icon displayed on the left side of a notification row.
  */
 @RemoteViews.RemoteView
 public class NotificationRowIconView extends CachingIconView {
+    private boolean mApplyCircularCrop = false;
+
     public NotificationRowIconView(Context context) {
         super(context);
     }
@@ -57,4 +66,82 @@
 
         super.onFinishInflate();
     }
+
+    @Nullable
+    @Override
+    Drawable loadSizeRestrictedIcon(@Nullable Icon icon) {
+        final Drawable original = super.loadSizeRestrictedIcon(icon);
+        final Drawable result;
+        if (mApplyCircularCrop) {
+            result = makeCircularDrawable(original);
+        } else {
+            result = original;
+        }
+
+        return result;
+    }
+
+    /**
+     * Enables circle crop that makes given image circular
+     */
+    @RemotableViewMethod(asyncImpl = "setApplyCircularCropAsync")
+    public void setApplyCircularCrop(boolean applyCircularCrop) {
+        mApplyCircularCrop = applyCircularCrop;
+    }
+
+    /**
+     * Async version of {@link NotificationRowIconView#setApplyCircularCrop}
+     */
+    public Runnable setApplyCircularCropAsync(boolean applyCircularCrop) {
+        mApplyCircularCrop = applyCircularCrop;
+        return () -> {
+        };
+    }
+
+    @Nullable
+    private Drawable makeCircularDrawable(@Nullable Drawable original) {
+        if (original == null) {
+            return original;
+        }
+
+        final Bitmap source = drawableToBitmap(original);
+
+        int size = Math.min(source.getWidth(), source.getHeight());
+
+        Bitmap squared = Bitmap.createScaledBitmap(source, size, size, /* filter= */ false);
+        Bitmap result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+
+        final Canvas canvas = new Canvas(result);
+        final Paint paint = new Paint();
+        paint.setShader(
+                new BitmapShader(squared, BitmapShader.TileMode.CLAMP,
+                        BitmapShader.TileMode.CLAMP));
+        paint.setAntiAlias(true);
+        float radius = size / 2f;
+        canvas.drawCircle(radius, radius, radius, paint);
+        return new BitmapDrawable(getResources(), result);
+    }
+
+    private static Bitmap drawableToBitmap(Drawable drawable) {
+        if (drawable instanceof BitmapDrawable bitmapDrawable) {
+            final Bitmap bitmap = bitmapDrawable.getBitmap();
+            if (bitmap.getConfig() == Bitmap.Config.HARDWARE) {
+                return bitmap.copy(Bitmap.Config.ARGB_8888, false);
+            } else {
+                return bitmap;
+            }
+        }
+
+        int width = drawable.getIntrinsicWidth();
+        width = width > 0 ? width : 1;
+        int height = drawable.getIntrinsicHeight();
+        height = height > 0 ? height : 1;
+
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+        drawable.draw(canvas);
+
+        return bitmap;
+    }
 }
diff --git a/core/java/com/android/internal/widget/TEST_MAPPING b/core/java/com/android/internal/widget/TEST_MAPPING
new file mode 100644
index 0000000..91cecfd
--- /dev/null
+++ b/core/java/com/android/internal/widget/TEST_MAPPING
@@ -0,0 +1,19 @@
+{
+  // v2/sysui/suite/test-mapping-sysui-screenshot-test
+  "sysui-screenshot-test": [
+    {
+      "name": "SystemUIGoogleScreenshotTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "android.platform.test.annotations.Postsubmit"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 80a7599..d9c40c3 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -483,4 +483,8 @@
         "libnativehelper",
         "libvintf",
     ],
+
+    required: [
+        "vintf",
+    ],
 }
diff --git a/core/jni/android_hardware_display_DisplayViewport.cpp b/core/jni/android_hardware_display_DisplayViewport.cpp
index 7f630cb..5d7b33e 100644
--- a/core/jni/android_hardware_display_DisplayViewport.cpp
+++ b/core/jni/android_hardware_display_DisplayViewport.cpp
@@ -59,7 +59,8 @@
     static const jclass intClass = FindClassOrDie(env, "java/lang/Integer");
     static const jmethodID byteValue = env->GetMethodID(intClass, "byteValue", "()B");
 
-    viewport->displayId = env->GetIntField(viewportObj, gDisplayViewportClassInfo.displayId);
+    viewport->displayId = ui::LogicalDisplayId{
+            env->GetIntField(viewportObj, gDisplayViewportClassInfo.displayId)};
     viewport->isActive = env->GetBooleanField(viewportObj, gDisplayViewportClassInfo.isActive);
     jint orientation = env->GetIntField(viewportObj, gDisplayViewportClassInfo.orientation);
     viewport->orientation = static_cast<ui::Rotation>(orientation);
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index bed7768..69f6334 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -165,8 +165,8 @@
     mInfo.ownerUid = gui::Uid{
             static_cast<uid_t>(env->GetIntField(obj, gInputWindowHandleClassInfo.ownerUid))};
     mInfo.packageName = getStringField(env, obj, gInputWindowHandleClassInfo.packageName, "<null>");
-    mInfo.displayId = env->GetIntField(obj,
-            gInputWindowHandleClassInfo.displayId);
+    mInfo.displayId =
+            ui::LogicalDisplayId{env->GetIntField(obj, gInputWindowHandleClassInfo.displayId)};
 
     jobject inputApplicationHandleObj = env->GetObjectField(obj,
             gInputWindowHandleClassInfo.inputApplicationHandle);
diff --git a/core/jni/android_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp
index 0e3c510..c786652 100644
--- a/core/jni/android_os_HwParcel.cpp
+++ b/core/jni/android_os_HwParcel.cpp
@@ -235,6 +235,10 @@
     return mWasSent;
 }
 
+void JHwParcel::addBlob(const sp<JHwBlob> &blob) {
+    mBlobs.emplace_back(blob);
+}
+
 }  // namespace android
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -1059,6 +1063,7 @@
         JHwParcel::GetNativeContext(env, thiz)->getParcel();
 
     sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, blobObj);
+    JHwParcel::GetNativeContext(env, thiz)->addBlob(blob);
     status_t err = blob->writeToParcel(parcel);
 
     if (err != OK) {
diff --git a/core/jni/android_os_HwParcel.h b/core/jni/android_os_HwParcel.h
index 2c26993..07d144a 100644
--- a/core/jni/android_os_HwParcel.h
+++ b/core/jni/android_os_HwParcel.h
@@ -17,14 +17,15 @@
 #ifndef ANDROID_OS_HW_PARCEL_H
 #define ANDROID_OS_HW_PARCEL_H
 
-#include "hwbinder/EphemeralStorage.h"
-
 #include <android-base/macros.h>
 #include <hwbinder/IBinder.h>
 #include <hwbinder/Parcel.h>
 #include <jni.h>
 #include <utils/RefBase.h>
 
+#include "android_os_HwBlob.h"
+#include "hwbinder/EphemeralStorage.h"
+
 namespace android {
 
 struct JHwParcel : public RefBase {
@@ -44,6 +45,8 @@
 
     EphemeralStorage *getStorage();
 
+    void addBlob(const sp<JHwBlob> &blob);
+
     void setTransactCallback(::android::hardware::IBinder::TransactCallback cb);
 
     void send();
@@ -60,6 +63,7 @@
 
     ::android::hardware::IBinder::TransactCallback mTransactCallback;
     bool mWasSent;
+    std::vector<sp<JHwBlob>> mBlobs;
 
     DISALLOW_COPY_AND_ASSIGN(JHwParcel);
 };
diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp
index 8dc9d0a..7a4854b 100644
--- a/core/jni/android_os_VintfObject.cpp
+++ b/core/jni/android_os_VintfObject.cpp
@@ -32,6 +32,8 @@
 static jmethodID gHashMapPut;
 static jclass gLongClazz;
 static jmethodID gLongValueOf;
+static jclass gVintfObjectClazz;
+static jmethodID gRunCommand;
 
 namespace android {
 
@@ -47,6 +49,56 @@
 using vintf::Vndk;
 using vintf::CheckFlags::ENABLE_ALL_CHECKS;
 
+// Instead of VintfObject::GetXxx(), we construct
+// HalManifest/CompatibilityMatrix objects by calling `vintf` through
+// UiAutomation.executeShellCommand() so that the commands are executed
+// using shell identity. Otherwise, we would need to allow "apps" to access
+// files like apex-info-list.xml which we don't want to open to apps.
+// This is okay because VintfObject is @TestApi and only used in CTS tests.
+
+static std::string runCmd(JNIEnv* env, const char* cmd) {
+    jstring jstr = (jstring)env->CallStaticObjectMethod(gVintfObjectClazz, gRunCommand,
+                                                        env->NewStringUTF(cmd));
+    std::string output;
+    if (jstr) {
+        auto cstr = env->GetStringUTFChars(jstr, nullptr);
+        output = std::string(cstr);
+        env->ReleaseStringUTFChars(jstr, cstr);
+    } else {
+        LOG(WARNING) << "Failed to run " << cmd;
+        env->ExceptionDescribe();
+        env->ExceptionClear();
+    }
+    return output;
+}
+
+template <typename T>
+static std::shared_ptr<const T> fromXml(const std::string& content) {
+    std::shared_ptr<T> object = std::make_unique<T>();
+    std::string error;
+    if (fromXml(object.get(), content, &error)) {
+        return object;
+    }
+    LOG(WARNING) << "Unabled to parse: " << error;
+    return nullptr;
+}
+
+static std::shared_ptr<const HalManifest> getDeviceHalManifest(JNIEnv* env) {
+    return fromXml<HalManifest>(runCmd(env, "vintf dm"));
+}
+
+static std::shared_ptr<const HalManifest> getFrameworkHalManifest(JNIEnv* env) {
+    return fromXml<HalManifest>(runCmd(env, "vintf fm"));
+}
+
+static std::shared_ptr<const CompatibilityMatrix> getDeviceCompatibilityMatrix(JNIEnv* env) {
+    return fromXml<CompatibilityMatrix>(runCmd(env, "vintf dcm"));
+}
+
+static std::shared_ptr<const CompatibilityMatrix> getFrameworkCompatibilityMatrix(JNIEnv* env) {
+    return fromXml<CompatibilityMatrix>(runCmd(env, "vintf fcm"));
+}
+
 template<typename V>
 static inline jobjectArray toJavaStringArray(JNIEnv* env, const V& v) {
     size_t i;
@@ -83,12 +135,10 @@
 {
     std::vector<std::string> cStrings;
 
-    tryAddSchema(VintfObject::GetDeviceHalManifest(), "device manifest", &cStrings);
-    tryAddSchema(VintfObject::GetFrameworkHalManifest(), "framework manifest", &cStrings);
-    tryAddSchema(VintfObject::GetDeviceCompatibilityMatrix(), "device compatibility matrix",
-                 &cStrings);
-    tryAddSchema(VintfObject::GetFrameworkCompatibilityMatrix(), "framework compatibility matrix",
-                 &cStrings);
+    tryAddSchema(getDeviceHalManifest(env), "device manifest", &cStrings);
+    tryAddSchema(getFrameworkHalManifest(env), "framework manifest", &cStrings);
+    tryAddSchema(getDeviceCompatibilityMatrix(env), "device compatibility matrix", &cStrings);
+    tryAddSchema(getFrameworkCompatibilityMatrix(env), "framework compatibility matrix", &cStrings);
 
     return toJavaStringArray(env, cStrings);
 }
@@ -108,15 +158,13 @@
 
 static jobjectArray android_os_VintfObject_getHalNamesAndVersions(JNIEnv* env, jclass) {
     std::set<std::string> halNames;
-    tryAddHalNamesAndVersions(VintfObject::GetDeviceHalManifest(),
-            "device manifest", &halNames);
-    tryAddHalNamesAndVersions(VintfObject::GetFrameworkHalManifest(),
-            "framework manifest", &halNames);
+    tryAddHalNamesAndVersions(getDeviceHalManifest(env), "device manifest", &halNames);
+    tryAddHalNamesAndVersions(getFrameworkHalManifest(env), "framework manifest", &halNames);
     return toJavaStringArray(env, halNames);
 }
 
 static jstring android_os_VintfObject_getSepolicyVersion(JNIEnv* env, jclass) {
-    std::shared_ptr<const HalManifest> manifest = VintfObject::GetDeviceHalManifest();
+    std::shared_ptr<const HalManifest> manifest = getDeviceHalManifest(env);
     if (manifest == nullptr || manifest->type() != SchemaType::DEVICE) {
         LOG(WARNING) << __FUNCTION__ << "Cannot get device manifest";
         return nullptr;
@@ -126,8 +174,7 @@
 }
 
 static jstring android_os_VintfObject_getPlatformSepolicyVersion(JNIEnv* env, jclass) {
-    std::shared_ptr<const CompatibilityMatrix> matrix =
-            VintfObject::GetFrameworkCompatibilityMatrix();
+    std::shared_ptr<const CompatibilityMatrix> matrix = getFrameworkCompatibilityMatrix(env);
     if (matrix == nullptr || matrix->type() != SchemaType::FRAMEWORK) {
         jniThrowRuntimeException(env, "Cannot get framework compatibility matrix");
         return nullptr;
@@ -148,7 +195,7 @@
 }
 
 static jobject android_os_VintfObject_getVndkSnapshots(JNIEnv* env, jclass) {
-    std::shared_ptr<const HalManifest> manifest = VintfObject::GetFrameworkHalManifest();
+    std::shared_ptr<const HalManifest> manifest = getFrameworkHalManifest(env);
     if (manifest == nullptr || manifest->type() != SchemaType::FRAMEWORK) {
         LOG(WARNING) << __FUNCTION__ << "Cannot get framework manifest";
         return nullptr;
@@ -163,7 +210,7 @@
 }
 
 static jobject android_os_VintfObject_getTargetFrameworkCompatibilityMatrixVersion(JNIEnv* env, jclass) {
-    std::shared_ptr<const HalManifest> manifest = VintfObject::GetDeviceHalManifest();
+    std::shared_ptr<const HalManifest> manifest = getDeviceHalManifest(env);
     if (manifest == nullptr || manifest->level() == Level::UNSPECIFIED) {
         return nullptr;
     }
@@ -188,19 +235,20 @@
 
 const char* const kVintfObjectPathName = "android/os/VintfObject";
 
-int register_android_os_VintfObject(JNIEnv* env)
-{
-
+int register_android_os_VintfObject(JNIEnv* env) {
     gString = MakeGlobalRefOrDie(env, FindClassOrDie(env, "java/lang/String"));
     gHashMapClazz = MakeGlobalRefOrDie(env, FindClassOrDie(env, "java/util/HashMap"));
     gHashMapInit = GetMethodIDOrDie(env, gHashMapClazz, "<init>", "()V");
-    gHashMapPut = GetMethodIDOrDie(env, gHashMapClazz,
-            "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+    gHashMapPut = GetMethodIDOrDie(env, gHashMapClazz, "put",
+                                   "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
     gLongClazz = MakeGlobalRefOrDie(env, FindClassOrDie(env, "java/lang/Long"));
     gLongValueOf = GetStaticMethodIDOrDie(env, gLongClazz, "valueOf", "(J)Ljava/lang/Long;");
+    gVintfObjectClazz = MakeGlobalRefOrDie(env, FindClassOrDie(env, kVintfObjectPathName));
+    gRunCommand = GetStaticMethodIDOrDie(env, gVintfObjectClazz, "runShellCommand",
+                                         "(Ljava/lang/String;)Ljava/lang/String;");
 
     return RegisterMethodsOrDie(env, kVintfObjectPathName, gVintfObjectMethods,
-            NELEM(gVintfObjectMethods));
+                                NELEM(gVintfObjectMethods));
 }
 
 extern int register_android_os_VintfRuntimeInfo(JNIEnv* env);
diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp
index f82ebfe..17129d8 100644
--- a/core/jni/android_tracing_PerfettoDataSource.cpp
+++ b/core/jni/android_tracing_PerfettoDataSource.cpp
@@ -244,8 +244,8 @@
     return static_cast<jlong>(reinterpret_cast<uintptr_t>(&nativeDestroy));
 }
 
-void nativeFlush(JNIEnv* env, jclass clazz, jlong ds_ptr, jobjectArray packets) {
-    ALOG(LOG_DEBUG, LOG_TAG, "nativeFlush(%p)", (void*)ds_ptr);
+void nativeWritePackets(JNIEnv* env, jclass clazz, jlong ds_ptr, jobjectArray packets) {
+    ALOG(LOG_DEBUG, LOG_TAG, "nativeWritePackets(%p)", (void*)ds_ptr);
     sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(ds_ptr);
     datasource->WritePackets(env, packets);
 }
@@ -256,10 +256,12 @@
 }
 
 void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
-                              jint buffer_exhausted_policy) {
+                              jint buffer_exhausted_policy, jboolean will_notify_on_stop,
+                              jboolean no_flush) {
     sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(datasource_ptr);
 
     struct PerfettoDsParams params = PerfettoDsParamsDefault();
+    params.will_notify_on_stop = will_notify_on_stop;
     params.buffer_exhausted_policy = (PerfettoDsBufferExhaustedPolicy)buffer_exhausted_policy;
 
     params.user_arg = reinterpret_cast<void*>(datasource.get());
@@ -325,13 +327,15 @@
         datasource_instance->onStart(env);
     };
 
-    params.on_flush_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex, void*, void* inst_ctx,
-                            struct PerfettoDsOnFlushArgs*) {
-        JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+    if (!no_flush) {
+        params.on_flush_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex, void*,
+                                void* inst_ctx, struct PerfettoDsOnFlushArgs*) {
+            JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
 
-        auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
-        datasource_instance->onFlush(env);
-    };
+            auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+            datasource_instance->onFlush(env);
+        };
+    }
 
     params.on_stop_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex inst_id, void* user_arg,
                            void* inst_ctx, struct PerfettoDsOnStopArgs*) {
@@ -422,7 +426,7 @@
          (void*)nativeCreate},
         {"nativeFlushAll", "(J)V", (void*)nativeFlushAll},
         {"nativeGetFinalizer", "()J", (void*)nativeGetFinalizer},
-        {"nativeRegisterDataSource", "(JI)V", (void*)nativeRegisterDataSource},
+        {"nativeRegisterDataSource", "(JIZZ)V", (void*)nativeRegisterDataSource},
         {"nativeGetPerfettoInstanceLocked", "(JI)Landroid/tracing/perfetto/DataSourceInstance;",
          (void*)nativeGetPerfettoInstanceLocked},
         {"nativeReleasePerfettoInstanceLocked", "(JI)V",
@@ -431,11 +435,12 @@
         {"nativePerfettoDsTraceIterateBegin", "(J)Z", (void*)nativePerfettoDsTraceIterateBegin},
         {"nativePerfettoDsTraceIterateNext", "(J)Z", (void*)nativePerfettoDsTraceIterateNext},
         {"nativePerfettoDsTraceIterateBreak", "(J)V", (void*)nativePerfettoDsTraceIterateBreak},
-        {"nativeGetPerfettoDsInstanceIndex", "(J)I", (void*)nativeGetPerfettoDsInstanceIndex}};
+        {"nativeGetPerfettoDsInstanceIndex", "(J)I", (void*)nativeGetPerfettoDsInstanceIndex},
+
+        {"nativeWritePackets", "(J[[B)V", (void*)nativeWritePackets}};
 
 const JNINativeMethod gMethodsTracingContext[] = {
         /* name, signature, funcPtr */
-        {"nativeFlush", "(J[[B)V", (void*)nativeFlush},
         {"nativeGetCustomTls", "(J)Ljava/lang/Object;", (void*)nativeGetCustomTls},
         {"nativeGetIncrementalState", "(J)Ljava/lang/Object;", (void*)nativeGetIncrementalState},
         {"nativeSetCustomTls", "(JLjava/lang/Object;)V", (void*)nativeSetCustomTls},
diff --git a/core/jni/android_view_KeyEvent.cpp b/core/jni/android_view_KeyEvent.cpp
index ca8752f..06e0d2d 100644
--- a/core/jni/android_view_KeyEvent.cpp
+++ b/core/jni/android_view_KeyEvent.cpp
@@ -135,8 +135,8 @@
     jlong eventTime = env->GetLongField(eventObj, gKeyEventClassInfo.mEventTime);
 
     KeyEvent event;
-    event.initialize(id, deviceId, source, displayId, *hmac, action, flags, keyCode, scanCode,
-                     metaState, repeatCount, downTime, eventTime);
+    event.initialize(id, deviceId, source, ui::LogicalDisplayId{displayId}, *hmac, action, flags,
+                     keyCode, scanCode, metaState, repeatCount, downTime, eventTime);
     return event;
 }
 
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 3e3af40..f914bee 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -22,7 +22,6 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
 #include <attestation/HmacKeyManager.h>
-#include <gui/constants.h>
 #include <input/Input.h>
 #include <log/log.h>
 #include <nativehelper/JNIHelp.h>
@@ -367,8 +366,8 @@
     ui::Transform transform;
     transform.set(xOffset, yOffset);
     ui::Transform identityTransform;
-    event->initialize(InputEvent::nextId(), deviceId, source, displayId, INVALID_HMAC, action, 0,
-                      flags, edgeFlags, metaState, buttonState,
+    event->initialize(InputEvent::nextId(), deviceId, source, ui::LogicalDisplayId{displayId},
+                      INVALID_HMAC, action, 0, flags, edgeFlags, metaState, buttonState,
                       static_cast<MotionClassification>(classification), transform, xPrecision,
                       yPrecision, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                       AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, downTimeNanos,
@@ -646,13 +645,13 @@
 
 static jint android_view_MotionEvent_nativeGetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
-    return event->getDisplayId();
+    return static_cast<jint>(event->getDisplayId().val());
 }
 
 static void android_view_MotionEvent_nativeSetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
                                                         jint displayId) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
-    return event->setDisplayId(displayId);
+    event->setDisplayId(ui::LogicalDisplayId{displayId});
 }
 
 static jint android_view_MotionEvent_nativeGetAction(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
diff --git a/core/jni/android_window_WindowInfosListener.cpp b/core/jni/android_window_WindowInfosListener.cpp
index bc69d1e6..c39d5e2 100644
--- a/core/jni/android_window_WindowInfosListener.cpp
+++ b/core/jni/android_window_WindowInfosListener.cpp
@@ -60,7 +60,7 @@
     }
     ScopedLocalRef<jobject> matrixObj(env, AMatrix_newInstance(env, transformValues));
     return env->NewObject(gDisplayInfoClassInfo.clazz, gDisplayInfoClassInfo.ctor,
-                          displayInfo.displayId, displayInfo.logicalWidth,
+                          displayInfo.displayId.val(), displayInfo.logicalWidth,
                           displayInfo.logicalHeight, matrixObj.get());
 }
 
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 12d62cc..062fab3 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -116,7 +116,7 @@
 
 using android::zygote::ZygoteFailure;
 
-using Action = android_mallopt_gwp_asan_options_t::Action;
+using Mode = android_mallopt_gwp_asan_options_t::Mode;
 
 // This type is duplicated in fd_utils.h
 typedef const std::function<void(std::string)>& fail_fn_t;
@@ -2101,21 +2101,21 @@
     switch (runtime_flags & RuntimeFlags::GWP_ASAN_LEVEL_MASK) {
         default:
         case RuntimeFlags::GWP_ASAN_LEVEL_DEFAULT:
-            gwp_asan_options.desire = GetBoolProperty(kGwpAsanAppRecoverableSysprop, true)
-                    ? Action::TURN_ON_FOR_APP_SAMPLED_NON_CRASHING
-                    : Action::DONT_TURN_ON_UNLESS_OVERRIDDEN;
+            gwp_asan_options.mode = GetBoolProperty(kGwpAsanAppRecoverableSysprop, true)
+                    ? Mode::APP_MANIFEST_DEFAULT
+                    : Mode::APP_MANIFEST_NEVER;
             android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options));
             break;
         case RuntimeFlags::GWP_ASAN_LEVEL_NEVER:
-            gwp_asan_options.desire = Action::DONT_TURN_ON_UNLESS_OVERRIDDEN;
+            gwp_asan_options.mode = Mode::APP_MANIFEST_NEVER;
             android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options));
             break;
         case RuntimeFlags::GWP_ASAN_LEVEL_ALWAYS:
-            gwp_asan_options.desire = Action::TURN_ON_FOR_APP;
+            gwp_asan_options.mode = Mode::APP_MANIFEST_ALWAYS;
             android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options));
             break;
         case RuntimeFlags::GWP_ASAN_LEVEL_LOTTERY:
-            gwp_asan_options.desire = Action::TURN_ON_WITH_SAMPLING;
+            gwp_asan_options.mode = Mode::APP_MANIFEST_DEFAULT;
             android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options));
             break;
     }
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 0433855..acef609 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -329,7 +329,8 @@
 
         InputDeviceInfo info = InputDeviceInfo();
         info.initialize(keyboardId, 0, 0, InputDeviceIdentifier(),
-                        "keyboard " + std::to_string(keyboardId), true, false, 0);
+                        "keyboard " + std::to_string(keyboardId), true, false,
+                        ui::LogicalDisplayId::DEFAULT);
         info.setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC);
         info.setKeyCharacterMap(*charMap);
 
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index c92435f..654d83c 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -607,7 +607,7 @@
 
     optional InsetsSourceProviderProto insets_source_provider = 1;
     optional WindowStateProto ime_target_from_ime = 2;
-    optional bool is_ime_layout_drawn = 3;
+    optional bool is_ime_layout_drawn = 3 [deprecated=true];
 }
 
 message BackNavigationProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index bfbfb3a..8541704 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -299,6 +299,9 @@
 
     <protected-broadcast android:name="android.hardware.display.action.WIFI_DISPLAY_STATUS_CHANGED" />
 
+    <protected-broadcast android:name="android.hardware.hdmi.action.OSD_MESSAGE" />
+    <protected-broadcast android:name="android.hardware.hdmi.action.ON_ACTIVE_SOURCE_RECOVERED_DISMISS_UI" />
+
     <protected-broadcast android:name="android.hardware.usb.action.USB_STATE" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_PORT_CHANGED" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_PORT_COMPLIANCE_CHANGED" />
@@ -8771,6 +8774,7 @@
 
         <service android:name="com.android.server.companion.datatransfer.contextsync.CallMetadataSyncInCallService"
                  android:permission="android.permission.BIND_INCALL_SERVICE"
+                 android:enabled="@bool/config_enableContextSyncInCall"
                  android:exported="true">
             <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS"
                        android:value="true" />
diff --git a/core/res/res/drawable/ic_signal_cellular_1_4_bar.xml b/core/res/res/drawable/ic_signal_cellular_1_4_bar.xml
index c0fe536..c692967 100644
--- a/core/res/res/drawable/ic_signal_cellular_1_4_bar.xml
+++ b/core/res/res/drawable/ic_signal_cellular_1_4_bar.xml
@@ -22,7 +22,11 @@
     <path
         android:fillColor="@android:color/white"
         android:pathData="M20,7v13H7L20,7 M22,2L2,22h20V2L22,2z" />
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M 11 13 L 2 22 L 11 22 Z" />
+    <clip-path android:name="triangle" android:pathData="M21,5 V21 H5 z">
+        <!-- 1 bar. move to higher ground. -->
+        <path
+            android:name="ic_signal_cellular_1_4_bar"
+            android:fillColor="@android:color/white"
+            android:pathData="M0,0 H11 V24 H0 z" />
+    </clip-path>
 </vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_signal_cellular_1_5_bar.xml b/core/res/res/drawable/ic_signal_cellular_1_5_bar.xml
index 816da22..b01c269 100644
--- a/core/res/res/drawable/ic_signal_cellular_1_5_bar.xml
+++ b/core/res/res/drawable/ic_signal_cellular_1_5_bar.xml
@@ -22,7 +22,11 @@
     <path
         android:fillColor="@android:color/white"
         android:pathData="M20,7V20H7L20,7m2-5L2,22H22V2Z" />
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M8.72,15.28,2,22H8.72V15.28Z" />
+    <clip-path android:name="triangle" android:pathData="M21,5 V21 H5 z">
+        <!-- 1 bar. might have to call you back. -->
+        <path
+            android:name="ic_signal_cellular_1_5_bar"
+            android:fillColor="@android:color/white"
+            android:pathData="M0,0 H12 V24 H0 z" />
+    </clip-path>
 </vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_signal_cellular_2_4_bar.xml b/core/res/res/drawable/ic_signal_cellular_2_4_bar.xml
index 69a966b..982623d 100644
--- a/core/res/res/drawable/ic_signal_cellular_2_4_bar.xml
+++ b/core/res/res/drawable/ic_signal_cellular_2_4_bar.xml
@@ -22,7 +22,11 @@
     <path
         android:fillColor="@android:color/white"
         android:pathData="M20,7v13H7L20,7 M22,2L2,22h20V2L22,2z" />
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M 13 11 L 2 22 L 13 22 Z" />
+    <clip-path android:name="triangle" android:pathData="M21,5 V21 H5 z">
+        <!-- 2 bars. 2 out of 4 ain't bad. -->
+        <path
+            android:name="ic_signal_cellular_2_4_bar"
+            android:fillColor="@android:color/white"
+            android:pathData="M0,0 H14 V24 H0 z" />
+    </clip-path>
 </vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_signal_cellular_2_5_bar.xml b/core/res/res/drawable/ic_signal_cellular_2_5_bar.xml
index 02c7a43..75daadd 100644
--- a/core/res/res/drawable/ic_signal_cellular_2_5_bar.xml
+++ b/core/res/res/drawable/ic_signal_cellular_2_5_bar.xml
@@ -23,7 +23,11 @@
     <path
         android:fillColor="@android:color/white"
         android:pathData="M20,7V20H7L20,7m2-5L2,22H22V2Z" />
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M 11.45 12.55 L 2 22 L 11.45 22 L 11.45 12.55 Z" />
+    <clip-path android:name="triangle" android:pathData="M21,5 V21 H5 z">
+        <!-- 2 bars. hanging in there. -->
+        <path
+            android:name="ic_signal_cellular_2_5_bar"
+            android:fillColor="@android:color/white"
+            android:pathData="M0,0 H14 V24 H0 z" />
+    </clip-path>
 </vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_signal_cellular_3_4_bar.xml b/core/res/res/drawable/ic_signal_cellular_3_4_bar.xml
index 46ce47c..4e4bea3 100644
--- a/core/res/res/drawable/ic_signal_cellular_3_4_bar.xml
+++ b/core/res/res/drawable/ic_signal_cellular_3_4_bar.xml
@@ -22,7 +22,11 @@
     <path
         android:fillColor="@android:color/white"
         android:pathData="M20,7v13H7L20,7 M22,2L2,22h20V2L22,2z" />
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M 2 22 L 16 22 L 16 21 L 16 20 L 16 11 L 16 10 L 16 8 Z" />
+    <clip-path android:name="triangle" android:pathData="M21,5 V21 H5 z">
+        <!-- 3 bars. quite nice. -->
+        <path
+            android:name="ic_signal_cellular_3_4_bar"
+            android:fillColor="@android:color/white"
+            android:pathData="M0,0 H17 V24 H0 z" />
+    </clip-path>
 </vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_signal_cellular_3_5_bar.xml b/core/res/res/drawable/ic_signal_cellular_3_5_bar.xml
index 37435e6b..9a98c29 100644
--- a/core/res/res/drawable/ic_signal_cellular_3_5_bar.xml
+++ b/core/res/res/drawable/ic_signal_cellular_3_5_bar.xml
@@ -22,7 +22,11 @@
     <path
         android:fillColor="@android:color/white"
         android:pathData="M20,7V20H7L20,7m2-5L2,22H22V2Z" />
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M 14.96 9.04 L 2 22 L 14.96 22 L 14.96 9.04 Z" />
+    <clip-path android:name="triangle" android:pathData="M21,5 V21 H5 z">
+        <!-- 3 bars. not great, not terrible. -->
+        <path
+            android:name="ic_signal_cellular_3_5_bar"
+            android:fillColor="@android:color/white"
+            android:pathData="M0,0 H16 V24 H0 z" />
+    </clip-path>
 </vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_signal_cellular_4_5_bar.xml b/core/res/res/drawable/ic_signal_cellular_4_5_bar.xml
index 6dc3646..2a37d01 100644
--- a/core/res/res/drawable/ic_signal_cellular_4_5_bar.xml
+++ b/core/res/res/drawable/ic_signal_cellular_4_5_bar.xml
@@ -22,7 +22,11 @@
     <path
         android:fillColor="@android:color/white"
         android:pathData="M20,7V20H7L20,7m2-5L2,22H22V2Z" />
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M 18.48 5.52 L 2 22 L 18.48 22 L 18.48 5.52 Z" />
+    <clip-path android:name="triangle" android:pathData="M21,5 V21 H5 z">
+        <!-- 4 bars. extremely respectable. -->
+        <path
+            android:name="ic_signal_cellular_4_5_bar"
+            android:fillColor="@android:color/white"
+            android:pathData="M0,0 H18 V24 H0 z" />
+    </clip-path>
 </vector>
\ No newline at end of file
diff --git a/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml b/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml
new file mode 100644
index 0000000..3b288d7
--- /dev/null
+++ b/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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
+  -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/status_bar_latest_event_content"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/notification_header_height"
+    android:clipChildren="false"
+    android:tag="compactMessagingHUN"
+    android:gravity="center_vertical"
+    android:theme="@style/Theme.DeviceDefault.Notification"
+    android:importantForAccessibility="no">
+    <com.android.internal.widget.NotificationRowIconView
+        android:id="@+id/icon"
+        android:layout_width="@dimen/notification_icon_circle_size"
+        android:layout_height="@dimen/notification_icon_circle_size"
+        android:layout_gravity="center_vertical|start"
+        android:layout_marginStart="@dimen/notification_icon_circle_start"
+        android:background="@drawable/notification_icon_circle"
+        android:padding="@dimen/notification_icon_circle_padding"
+        android:maxDrawableWidth="@dimen/notification_icon_circle_size"
+        android:maxDrawableHeight="@dimen/notification_icon_circle_size"
+        />
+    <com.android.internal.widget.NotificationRowIconView
+        android:id="@+id/conversation_icon"
+        android:layout_width="@dimen/notification_icon_circle_size"
+        android:layout_height="@dimen/notification_icon_circle_size"
+        android:layout_gravity="center_vertical|start"
+        android:layout_marginStart="@dimen/notification_icon_circle_start"
+        android:maxDrawableWidth="@dimen/notification_icon_circle_size"
+        android:maxDrawableHeight="@dimen/notification_icon_circle_size"
+        android:scaleType="centerCrop"
+        android:importantForAccessibility="no"
+        />
+    <FrameLayout
+        android:id="@+id/alternate_expand_target"
+        android:layout_width="@dimen/notification_content_margin_start"
+        android:layout_height="match_parent"
+        android:layout_gravity="start"
+        android:importantForAccessibility="no"
+        android:focusable="false"
+        />
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginStart="@dimen/notification_content_margin_start"
+        android:orientation="horizontal"
+        >
+        <NotificationTopLineView
+            android:id="@+id/notification_top_line"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_centerVertical="true"
+            android:layout_weight="1"
+            android:clipChildren="false"
+            android:gravity="center_vertical"
+            android:theme="@style/Theme.DeviceDefault.Notification"
+            >
+            <TextView
+                android:id="@+id/title"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="@dimen/notification_header_separating_margin"
+                android:ellipsize="end"
+                android:fadingEdge="horizontal"
+                android:singleLine="true"
+                android:textAlignment="viewStart"
+                android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+                />
+            <include layout="@layout/notification_top_line_views" />
+        </NotificationTopLineView>
+        <FrameLayout
+            android:id="@+id/reply_action_container"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/notification_action_list_height"
+            android:gravity="center_vertical"
+            android:orientation="horizontal" />
+        <FrameLayout
+            android:id="@+id/expand_button_touch_container"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:minWidth="@dimen/notification_content_margin_end"
+            >
+            <include layout="@layout/notification_expand_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical|end"
+                />
+        </FrameLayout>
+    </LinearLayout>
+</FrameLayout>
diff --git a/core/res/res/layout/side_fps_toast.xml b/core/res/res/layout/side_fps_toast.xml
index 96860b0..78299ab 100644
--- a/core/res/res/layout/side_fps_toast.xml
+++ b/core/res/res/layout/side_fps_toast.xml
@@ -18,28 +18,28 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
-              android:minWidth="350dp"
               android:layout_gravity="center"
+              android:minWidth="350dp"
               android:background="@color/side_fps_toast_background">
     <TextView
-        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:layout_width="0dp"
+        android:layout_weight="6"
+        android:paddingBottom="10dp"
         android:text="@string/fp_power_button_enrollment_title"
-        android:singleLine="true"
-        android:ellipsize="end"
         android:textColor="@color/side_fps_text_color"
         android:paddingLeft="20dp"/>
     <Space
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_weight="1"/>
+        android:layout_width="5dp"
+        android:layout_height="match_parent" />
     <Button
         android:id="@+id/turn_off_screen"
-        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:layout_width="0dp"
+        android:layout_weight="3"
+        android:paddingBottom="10dp"
         android:text="@string/fp_power_button_enrollment_button_text"
-        android:paddingRight="20dp"
         style="?android:attr/buttonBarNegativeButtonStyle"
         android:textColor="@color/side_fps_button_color"
-        android:maxLines="1"/>
+        />
 </LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index e1f5f87..2a3c691 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Roetinemodus-inligtingkennisgewing"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Batterybespaarder is aangeskakel"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Verminder tans batterygebruik om batterylewe te verleng"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Batterybespaarder is aan"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Batterybespaarder is aangeskakel om batterylewe te verleng"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Batterybespaarder"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Batterybespaarder is afgeskakel"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Foon se battery het genoeg krag. Kenmerke is nie meer beperk nie."</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Maak Boodskappe oop"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Hoe dit werk"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Hangend …"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index f97a2bc..08a290c 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"የዕለት ተዕለት ሁነታ መረጃ ማሳወቂያዎች"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"የባትሪ ኃይል ቆጣቢ በርቷል"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"የባትሪ ዕድሜን ለማራዘም የባትሪ አጠቃቀምን በመቀነስ ላይ"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"ባትሪ ቆጣቢ በርቷል"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"የባትሪ ዕድሜን ለማራዘም የባትሪ ኃይል ቆጣቢ በርቷል"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ባትሪ ቆጣቢ"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"የባትሪ ቆጣቢ ጠፍቷል"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"ስልክ በቂ የባትሪ ኃይል አለው። ባህሪያት ከእንግዲህ የተገደቡ አይደሉም።"</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"ሥራ 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"ሙከራ"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"የጋራ"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"የሥራ መገለጫ"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"የግል ቦታ"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"አባዛ"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"የጋራ"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"አደገኛ የማሳወቂያ ይዘት ተደብቋል"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ለደኅንነት ሲባል የመተግበሪያ ይዘት ከማያ ገጽ ማጋራት ተደብቋል"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"መልዕክቶች ይክፈቱ"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"እንዴት እንደሚሠራ"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"በመጠባበቅ ላይ..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 190a3ff..20d491f 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -165,8 +165,8 @@
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"تم الاتصال بشبكة \"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>\" المشفَّرة"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"‏أصبح الاتصال باستخدام شريحة SIM لشبكة \"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>\" أكثر أمانًا الآن"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"تم الاتصال بشبكة غير مشفَّرة"</string>
-    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"‏المكالمات والرسائل والبيانات هي أكثر عرضة للاختراق في الوقت الحالي أثناء استخدام شريحة SIM لشبكة \"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>\""</string>
-    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"‏المكالمات والرسائل والبيانات هي أكثر عرضة للاختراق في الوقت الحالي أثناء استخدام شريحة SIM لشبكة \"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>\".\n\nستتلقّى إشعارًا آخر عندما يتم تشفير اتصالك مرة أخرى."</string>
+    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"‏تكون المكالمات والرسائل والبيانات في الوقت الحالي أكثر عرضة للاختراق أثناء استخدام شريحة SIM من شبكة \"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>\""</string>
+    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"‏تكون المكالمات والرسائل والبيانات في الوقت الحالي أكثر عرضة للاختراق أثناء استخدام شريحة SIM من شبكة \"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>\".\n\nستتلقّى إشعارًا آخر عندما يتم تشفير اتصالك مرة أخرى."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"إعدادات أمان شبكة الجوّال"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"مزيد من المعلومات"</string>
     <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"حسنًا"</string>
@@ -849,7 +849,7 @@
     <string name="policylab_forceLock" msgid="7360335502968476434">"قفل الشاشة"</string>
     <string name="policydesc_forceLock" msgid="1008844760853899693">"التحكّم في طريقة ووقت قفل الشاشة"</string>
     <string name="policylab_wipeData" msgid="1359485247727537311">"محو جميع البيانات"</string>
-    <string name="policydesc_wipeData" product="tablet" msgid="7245372676261947507">"يمكنك محو بيانات الجهاز اللوحي بدون تحذير، وذلك عبر إجراء إعادة الضبط على الإعدادات الأصلية."</string>
+    <string name="policydesc_wipeData" product="tablet" msgid="7245372676261947507">"محو بيانات الجهاز اللوحي بدون تحذير، وذلك عبر إعادة الضبط على الإعدادات الأصلية"</string>
     <string name="policydesc_wipeData" product="tv" msgid="513862488950801261">"‏يمكنك محو بيانات جهاز Android TV بدون تحذير عن طريق تنفيذ إعادة الضبط على الإعدادات الأصلية."</string>
     <string name="policydesc_wipeData" product="automotive" msgid="660804547737323300">"يمكنك محو بيانات \"نظام الترفيه والمعلومات\" بدون تحذير، وذلك من خلال إعادة الضبط على الإعدادات الأصلية."</string>
     <string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"محو بيانات الهاتف بدون تحذير، وذلك من خلال إعادة ضبط البيانات على الإعدادات الأصلية"</string>
@@ -2148,10 +2148,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"إشعار معلومات \"وضع سلسلة الإجراءات\""</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"تم تفعيل ميزة توفير شحن البطارية"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"يتم تقليل استخدام البطارية لإطالة عمرها."</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"تم تفعيل ميزة \"توفير شحن البطارية\""</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"تم تفعيل ميزة \"توفير شحن البطارية\" لإطالة عمر البطارية"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"توفير شحن البطارية"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"تم تفعيل ميزة \"توفير شحن البطارية\""</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"تم شحن الهاتف بدرجة كافية وتفعيل الميزات مرة أخرى"</string>
@@ -2419,4 +2417,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"فتح تطبيق \"الرسائل\""</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"طريقة العمل"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"بانتظار الإزالة من الأرشيف…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 72eb2cc..30ced90 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"ৰুটিন ম’ডৰ তথ্য জাননী"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"বেটাৰী সঞ্চয়কাৰী অন কৰা হৈছে"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"বেটাৰীৰ জীৱনকাল বৃদ্ধি কৰিবলৈ বেটাৰীৰ ব্যৱহাৰ কমোৱা হৈছে"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"বেটাৰী সঞ্চয়কাৰী অন কৰা হৈছে"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"বেটাৰীৰ জীৱনকাল বৃদ্ধি কৰিবলৈ বেটাৰী সঞ্চয়কাৰী অন কৰা হৈছে"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"বেটাৰী সঞ্চয়কাৰী"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"বেটাৰী সঞ্চয়কাৰী অফ কৰা হ’ল"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"ফ\'নটোত পর্যাপ্ত পৰিমাণে চার্জ আছে। সুবিধাবোৰ আৰু সীমাবদ্ধ কৰা নাই।"</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"কৰ্মস্থান ৩"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"পৰীক্ষা"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"শ্বেয়াৰ কৰা"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"কৰ্মস্থানৰ প্ৰ’ফাইল"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"প্ৰাইভেট স্পে’চ"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ক্ল’ন"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"সম্প্ৰদায়ৰ সৈতে জড়িত"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"সংবেদনশীল জাননী লুকুওৱা হৈছে"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"সুৰক্ষাৰ বাবে এপৰ সমল স্ক্ৰীণ শ্বেয়াৰ কৰাৰ পৰা লুকুৱাই ৰখা হৈছে"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages খোলক"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ই কেনেকৈ কাম কৰে"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"বিবেচনাধীন হৈ আছে..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 3f99b5d..49073f1 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Rejim üçün məlumat bildirişi"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Enerjiyə Qənaət aktivdir"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Batareyanın ömrünü uzatmaq üçün batareyadan istifadəni azaldın"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Enerjiyə Qənaət yanılıdır"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Batareya istifadəsini artırmaq üçün Enerjiyə Qənaət yandırıldı"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Enerjiyə qənaət"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Enerjiyə qənaət deaktivdir"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefonun kifayət qədər enerjisi var. Funksiyalar artıq məhdud deyil."</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"İş 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Kommunal"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"İş profili"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Məxfi sahə"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Kommunal"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Həssas bildiriş kontenti gizlədildi"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Güvənlik üçün tətbiq kontenti ekran paylaşımından gizlədildi"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Mesajı açın"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Haqqında"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Gözləmədə..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 471befa..ca66ef4 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -161,7 +161,7 @@
     <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Mreža u blizini je u <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> evidentirala jedinstveni ID vašeg uređaja (IMSI ili IMEI) dok ste koristili <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM.\n\nTo znači da je evidentirala vašu lokaciju, aktivnost i identitet. To je uobičajena praksa, ali može da bude problem ljudima koji su zabrinuti za privatnost."</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Povezani ste na šifrovanu mrežu <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Veza <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM-a je sada bezbednija"</string>
-    <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Povezani ste na šifrovanu mrežu"</string>
+    <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Povezani ste na nešifrovanu mrežu"</string>
     <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Pozivi, poruke i podaci su trenutno ranjiviji dok koristite <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM"</string>
     <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Pozivi, poruke i podaci su trenutno ranjiviji dok koristite <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM.\n\nKada veza ponovo bude šifrovana, poslaćemo vam drugo obaveštenje."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Podešavanja bezbednosti na mobilnoj mreži"</string>
@@ -2145,10 +2145,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Obaveštenje o informacijama Rutinskog režima"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Ušteda baterije je uključena"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Smanjuje se potrošnja baterije da bi se produžilo njeno trajanje"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Ušteda baterije je uključena"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Ušteda baterije je uključena da bi se produžilo trajanje baterije"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Ušteda baterije"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Ušteda baterije je isključena"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Baterija telefona je dovoljno napunjena. Funkcije više nisu ograničene."</string>
@@ -2416,4 +2414,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvori Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Princip rada"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Na čekanju..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index a8b0344..4cd150a 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -162,7 +162,7 @@
     <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"У <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> у сетцы паблізу быў запісаны ўнікальны ідэнтыфікатар вашай прылады (IMSI або IMEI) пры выкарыстанні SIM-карты <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nГэта азначае, што даныя пра ваша месцазнаходжанне, дзеянні або асобу былі зарэгістраваны. Гэта звычайная практыка, але можа быць праблемай для людзей, якія турбуюцца аб прыватнасці."</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Падключана да зашыфраванай сеткі <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Цяпер падключэнне да SIM-карты <xliff:g id="NETWORK_NAME">%1$s</xliff:g> стала больш бяспечным"</string>
-    <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Падключана да зашыфраванай сеткі"</string>
+    <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Падключана да незашыфраванай сеткі"</string>
     <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Зараз выклікі, паведамленні і даныя менш абаронены пры выкарыстанні SIM-карты <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Зараз выклікі, паведамленні і даныя менш абаронены пры выкарыстанні SIM-карты <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nКалі падключэнне будзе зноў зашыфравана, вы атрымаеце апавяшчэнне."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Налады сеткавай бяспекі"</string>
@@ -2146,10 +2146,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Апавяшчэнне з інфармацыяй пра ўсталяваны рэжым"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Рэжым энергазберажэння ўключаны"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Абмежаванне выкарыстання зараду для падаўжэння часу працы ад акумулятара"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Рэжым энергазберажэння ўключаны"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Каб падоўжыць час працы ад акумулятара, уключаны рэжым энергазберажэння"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Рэжым энергазберажэння"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Рэжым эканоміі зараду выключаны"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"У тэлефона дастатковы ўзровень зараду. Функцыі больш не абмежаваны."</string>
@@ -2405,14 +2403,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Працоўны 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Тэставы"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Супольны"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Працоўны профіль"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Прыватная прастора"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Клон"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Супольны"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Канфідэнцыяльнае змесціва ў апавяшчэннях схавана"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Змесціва праграмы выключана з абагульвання экрана ў мэтах бяспекі"</string>
@@ -2421,4 +2415,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Адкрыць Паведамленні"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Як гэта працуе"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"У чаканні..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 2adebe2..eb2e920 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -161,7 +161,7 @@
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Установена е връзка с шифрованата мрежа <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Връзката със SIM картата от <xliff:g id="NETWORK_NAME">%1$s</xliff:g> вече е по-сигурна"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Установена е връзка с нешифрована мрежа"</string>
-    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Понастоящем обажданията, съобщенията и данните са по-уязвими, докато използвате SIM картата си от <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
+    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Обажданията, съобщенията и данните са по-уязвими, докато използвате SIM картата си от <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Понастоящем обажданията, съобщенията и данните са по-уязвими, докато използвате SIM картата си от <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nСлед като връзката ви бъде шифрована отново, ще получите друго известие."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Настройки за сигурност на мобилната мрежа"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Научете повече"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Известие с информация за режима на поредица"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Режимът за запазване на батерията е включен"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Използването на батерията се намалява с цел удължаване на живота ѝ"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Режимът за запазване на батерията е включен"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Режимът за запазване на батерията е включен с цел удължаване на живота ѝ"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Режим за запазване на батерията"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Режимът за запазване на батерията е изключен"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Батерията на телефона има достатъчно заряд. Функциите вече не са ограничени."</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Служебни 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Тестване"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Общи"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Служебен потребителски профил"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Частно пространство"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Клониране"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Общи"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Деликатното съдържание в известието е скрито"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Съдържанието на приложението е скрито от функцията за споделяне на екрана от съображения за сигурност"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Отваряне на Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Начин на работа"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Изчаква..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index aa061a9..6618127 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -157,7 +157,7 @@
     <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"এনক্রিপশন, এনক্রিপটেড নয় এমন নেটওয়ার্কের জন্য বিজ্ঞপ্তি"</string>
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"ডিভাইস আইডি অ্যাক্সেস করা হয়েছে"</string>
     <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"আপনার <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> সিম কার্ড ব্যবহার করে <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>-এ, আশেপাশের নেটওয়ার্ক আপনার ডিভাইসের অনন্য আইডি (IMSI অথবা IMEI) রেকর্ড করেছে"</string>
-    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"আপনার <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> সিম কার্ড ব্যবহার করে <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>-এ, আশেপাশের নেটওয়ার্ক আপনার ডিভাইসের অনন্য আইডি (IMSI অথবা IMEI) রেকর্ড করেছে।\n\nএটির মানে হল আপনার লোকেশন, অ্যাক্টিভিটি বা পরিচিতি লগ-ইন করা হয়েছে। এটি সাধারণ পদ্ধতি কিন্তু সেইসব লোকজনের জন্য সমস্যা হতে পারে যারা নিজেদের গোপনীয়তা নিয়ে উদ্বেগে থাকেন।"</string>
+    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"আপনার <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> সিম কার্ড ব্যবহার করে <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>-এ, আশেপাশের নেটওয়ার্ক আপনার ডিভাইসের অনন্য আইডি (IMSI অথবা IMEI) রেকর্ড করেছে।\n\nএটির মানে হল আপনার লোকেশন, অ্যাক্টিভিটি বা পরিচিতি লগ করা হয়েছে। এটি সাধারণ পদ্ধতি কিন্তু সেইসব লোকজনের জন্য সমস্যা হতে পারে যারা নিজেদের গোপনীয়তা নিয়ে উদ্বেগে থাকেন।"</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> এনক্রিপটেড নেটওয়ার্কের সাথে কানেক্ট করা রয়েছে"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"এখন <xliff:g id="NETWORK_NAME">%1$s</xliff:g> সিম কার্ডের কানেকশন আরও সুরক্ষিত"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"এনক্রিপটেড নয় এমন নেটওয়ার্কের সাথে কানেক্ট করা রয়েছে"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"রুটিন মোডের তথ্য সংক্রান্ত বিজ্ঞপ্তি"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"\'ব্যাটারি সেভার\' ফিচার চালু করা হয়েছে"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"ব্যাটারির আয়ু বাড়াতে ব্যাটারির ব্যবহার কমানো হচ্ছে"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"\'ব্যাটারি সেভার\' চালু করা হয়েছে"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"ব্যাটারির আয়ু বাড়াতে \'ব্যাটারি সেভার\' চালু করা হয়েছে"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ব্যাটারি সেভার"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"ব্যাটারি সেভার বন্ধ করা আছে"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"ফোনের ব্যাটারিতে যথেষ্ট চার্জ আছে। ফিচারগুলির উপর আর বিধিনিষেধ নেই।"</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"৩য় অফিস"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"পরীক্ষা"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"কমিউনাল"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"অফিস প্রোফাইল"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"প্রাইভেট স্পেস"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ক্লোন"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"কমিউনাল"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"সংবেদনশীল বিজ্ঞপ্তির কন্টেন্ট লুকানো আছে"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"নিরাপত্তার জন্য স্ক্রিন শেয়ার করা থেকে লুকানো অ্যাপের কন্টেন্ট"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages খুলুন"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"এটি কীভাবে কাজ করে"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"বাকি আছে…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 6ea579d..4f058bf 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -2145,10 +2145,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Obavještenje za informacije Rutinskog načina"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Ušteda baterije je uključena"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Smanjena je potrošnja baterije da se produži vijek trajanja baterije"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Ušteda baterije je uključena"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Ušteda baterije je uključena radi produženja vijeka trajanja baterije"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Ušteda baterije"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Ušteda baterije je isključena"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefon se dovoljno napunio. Funkcije nisu više ograničene."</string>
@@ -2404,14 +2402,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"3. poslovno"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Testno"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Opće"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Radni profil"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privatni prostor"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Opće"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Sakriven je osjetljiv sadržaj obavještenja"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Sadržaj aplikacije je sakriven od dijeljenja ekrana radi sigurnosti"</string>
@@ -2420,4 +2414,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvorite Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kako ovo funkcionira"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Na čekanju…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 1778a3b..77fd6b8 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -2145,10 +2145,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificació d\'informació del mode de rutina"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Estalvi de bateria s\'ha activat"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"S\'està reduint l\'ús de la bateria per allargar-ne la durada"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"La funció Estalvi de bateria està activada"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"S\'ha activat la funció Estalvi de bateria per allargar la durada de la bateria"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Estalvi de bateria"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"L\'estalvi de bateria s\'ha desactivat"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"El telèfon té prou bateria. Les funcions ja no estan restringides."</string>
@@ -2416,4 +2414,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Obre Missatges"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Com funciona"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendent..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 4e0938c..5615f79 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -2146,10 +2146,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Informační oznámení režimu sledu činností"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Zapnul se spořič baterie"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Je třeba snížit využití baterie za účelem prodloužení její výdrže"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Spořič baterie je zapnutý"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Spořič baterie je zapnutý a prodlužuje výdrž baterie"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Spořič baterie"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Spořič baterie je vypnutý"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefon je dostatečně nabitý. Funkce už nejsou omezeny."</string>
@@ -2405,14 +2403,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Práce 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Komunální"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Pracovní profil"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Soukromý prostor"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Komunální"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Obsah citlivých oznámení je skrytý"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Obsah aplikace je z bezpečnostních důvodů při sdílení obrazovky skryt"</string>
@@ -2421,4 +2415,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otevřít Zprávy"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Jak to funguje"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Čeká na vyřízení…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 286adca..fd04e42 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notifikation med oplysninger om rutinetilstand"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Batterisparefunktion er aktiveret"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reducerer batteriforbruget for at forlænge batteritiden"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Batterisparefunktion er aktiveret"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Batterisparefunktion er aktiveret for at forlænge batteritiden"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Batterisparefunktion"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Batterisparefunktion blev slået fra"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefonen har tilstrækkeligt batteri. Funktionerne er ikke længere begrænsede."</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Åbn Beskeder"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Sådan fungerer det"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Afventer…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 6c5783a..630ec75 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -156,12 +156,12 @@
     <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Sicherheit des Mobilfunknetzes"</string>
     <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Verschlüsselung, Benachrichtigungen für unverschlüsselte Netzwerke"</string>
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Auf Geräte-ID zugegriffen"</string>
-    <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Um <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> hat ein Netzwerk in der Nähe die eindeutige ID (IMSI oder IMEI) deines Geräts aufgezeichnet, während du deine <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-SIM verwendet hast."</string>
-    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Um <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> hat ein Netzwerk in der Nähe die eindeutige ID (IMSI oder IMEI) deines Geräts aufgezeichnet, während du deine <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-SIM verwendet hast.\n\nDaher wurden dein Standort, deine Aktivitäten oder deine Identität protokolliert. Das ist zwar üblich, kann jedoch ein Problem für Personen sein, denen ihre Privatsphäre wichtig ist."</string>
+    <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Um <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> hat ein Netzwerk in der Nähe die eindeutige ID (IMSI oder IMEI) deines Geräts aufgezeichnet, während du <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-SIM verwendet hast."</string>
+    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Um <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> hat ein Netzwerk in der Nähe die eindeutige ID (IMSI oder IMEI) deines Geräts aufgezeichnet, während du <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-SIM verwendet hast.\n\nDaher wurden dein Standort, deine Aktivitäten oder deine Identität protokolliert. Das ist zwar üblich, kann jedoch ein Problem für Personen sein, denen ihre Privatsphäre wichtig ist."</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Mit verschlüsseltem Netzwerk „<xliff:g id="NETWORK_NAME">%1$s</xliff:g>“ verbunden"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Verbindung der <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-SIM ist jetzt sicherer"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Mit unverschlüsseltem Netzwerk verbunden"</string>
-    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Anrufe, Nachrichten und Daten sind momentan anfälliger für Angriffe, während du deine <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-SIM verwendest"</string>
+    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Anrufe, Nachrichten und Daten sind anfälliger für Angriffe, während du <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-SIM verwendest"</string>
     <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Anrufe, Nachrichten und Daten sind momentan anfälliger für Angriffe, während du deine <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-SIM verwendest.\n\nWenn deine Verbindung wieder verschlüsselt ist, erhältst du eine weitere Benachrichtigung."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Einstellungen für die Sicherheit des Mobilfunknetzes"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Weitere Informationen"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Infomitteilung zum Ablaufmodus"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Energiesparmodus aktiviert"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reduzierung der Akkunutzung, um die Akkulaufzeit zu verlängern"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Energiesparmodus ist aktiviert"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Der Energiesparmodus ist aktiviert, um die Akkulaufzeit zu verlängern"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Energiesparmodus"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Energiesparmodus deaktiviert"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Das Smartphone ist ausreichend geladen. Es sind keine Funktionen mehr beschränkt."</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Geschäftlich 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Gemeinsam genutzt"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Arbeitsprofil"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Vertrauliches Profil"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Gemeinsam genutzt"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Vertrauliche Benachrichtigungsinhalte ausgeblendet"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"App-Inhalte werden aus Sicherheitsgründen bei der Bildschirmfreigabe ausgeblendet"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages öffnen"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"So funktionierts"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Ausstehend…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 80a4ef2..5490131 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Ειδοποίηση πληροφοριών λειτουργίας Ρουτίνας"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Η Εξοικονόμηση μπαταρίας ενεργοποιήθηκε"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Μείωση της χρήσης της μπαταρίας για παράταση της διάρκειας ζωής της μπαταρίας"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Η Εξοικονόμηση μπαταρίας είναι ενεργή"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Η Εξοικονόμηση μπαταρίας ενεργοποιήθηκε για την παράταση της διάρκειας ζωής της μπαταρίας"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Εξοικονόμηση μπαταρίας"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Η Εξοικονόμηση μπαταρίας απενεργοποιήθηκε"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Το τηλέφωνο είναι αρκετά φορτισμένο. Οι λειτουργίες δεν περιορίζονται πλέον."</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Εργασία 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Δοκιμή"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Κοινόχρηστο"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Προφίλ εργασίας"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Απόρρητος χώρος"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Κλώνος"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Κοινόχρηστο"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Έγινε απόκρυψη της ειδοποίησης ευαίσθητου περιεχομένου"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Για λόγους ασφάλειας, έγινε απόκρυψη του περιεχομένου της εφαρμογής από την κοινή χρήση οθόνης"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Άνοιγμα Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Πώς λειτουργεί"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Σε εκκρεμότητα…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index a2c2a7c..a32fcca 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Routine Mode info notification"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Battery Saver turned on"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reducing battery usage to extend battery life"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Battery Saver is on"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Battery Saver is turned on to extend battery life"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Battery Saver"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Battery Saver turned off"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Phone has enough charge. Features no longer restricted."</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 8d39204..9f06f71 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Routine Mode info notification"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Battery Saver turned on"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reducing battery usage to extend battery life"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Battery Saver is on"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Battery Saver is turned on to extend battery life"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Battery Saver"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Battery Saver turned off"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Phone has enough charge. Features no longer restricted."</string>
@@ -2415,4 +2413,13 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending..."</string>
+    <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Set up Fingerprint Unlock again"</string>
+    <string name="fingerprint_dangling_notification_msg_1" msgid="6261149111900787302">"<xliff:g id="FINGERPRINT">%s</xliff:g> wasn\'t working well and was deleted to improve performance"</string>
+    <string name="fingerprint_dangling_notification_msg_2" msgid="7688302770424064884">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> and <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> weren\'t working well and were deleted to improve performance"</string>
+    <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> wasn\'t working well and was deleted. Set it up again to unlock your phone with fingerprint."</string>
+    <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> and <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> weren\'t working well and were deleted. Set them up again to unlock your phone with your fingerprint."</string>
+    <string name="face_dangling_notification_title" msgid="947852541060975473">"Set up Face Unlock again"</string>
+    <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Your face model wasn\'t working well and was deleted. Set it up again to unlock your phone with face."</string>
+    <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Set up"</string>
+    <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Not now"</string>
 </resources>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 967eb68..bfcc4be 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Routine Mode info notification"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Battery Saver turned on"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reducing battery usage to extend battery life"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Battery Saver is on"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Battery Saver is turned on to extend battery life"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Battery Saver"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Battery Saver turned off"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Phone has enough charge. Features no longer restricted."</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 997ccbd..8000732 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Routine Mode info notification"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Battery Saver turned on"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reducing battery usage to extend battery life"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Battery Saver is on"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Battery Saver is turned on to extend battery life"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Battery Saver"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Battery Saver turned off"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Phone has enough charge. Features no longer restricted."</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 151df50..0fe2ccc 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‎‏‏‏‎‎‏‏‏‎‏‏‎‎‎‏‏‏‎‎‎‎‎‏‎‏‏‎‎‏‏‏‏‎‎‎‎‎‏‏‎‎‏‎‎‏‎‏‎‏‏‎‎‎‎‎‎Routine Mode info notification‎‏‎‎‏‎"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‎‏‎‎‎‏‎‏‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‏‎‎‏‏‎‏‏‏‏‎‎‎‎‏‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‎Battery Saver turned on‎‏‎‎‏‎"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‏‏‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‎‏‏‎‏‎‏‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎Reducing battery usage to extend battery life‎‏‎‎‏‎"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‏‎‎‏‎‎‏‎‏‏‏‎‏‏‏‎‎‎‎‎‎‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‎‎‎‏‎‎‎‎‎‎Battery Saver is on‎‏‎‎‏‎"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‏‎‏‏‏‎‏‏‎‏‏‏‏‏‎‏‏‏‎‎‎‏‏‏‏‏‎‎‎‎‏‎‏‎‏‏‎‎‎‏‎‎‎‎‎‏‎‏‏‎‏‎‎‎‏‎‎Battery Saver is turned on to extend battery life‎‏‎‎‏‎"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‏‎‎‎‎‎‎‏‏‎‎‏‎‏‏‎‏‎‏‎‏‎‎‏‎‏‎‏‎‏‏‎‏‎‎‎‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‎‎‏‎Battery Saver‎‏‎‎‏‎"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‎‎‎‎‏‏‎‎‏‏‎‏‏‏‎‏‎‎‎‏‏‎‎‎‎‎‎‎‎‏‏‎Battery Saver turned off‎‏‎‎‏‎"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‏‏‎‎‎‏‏‏‎‏‏‏‏‎‏‎‎‎‏‏‎‏‏‏‏‎‏‏‎‏‎‎‎‎‏‏‎‏‎‏‏‎‎‏‎‏‎‏‏‎‏‏‏‏‎Phone has enough charge. Features no longer restricted.‎‏‎‎‏‎"</string>
@@ -2415,4 +2413,13 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‎‏‎‏‎‎‎‎‏‎‏‏‎‎‏‏‏‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‏‎‏‎‎‎‎‎‏‎Open Messages‎‏‎‎‏‎"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‏‎‏‏‏‎‏‎‏‏‎‎‎‎‏‏‏‎‏‎‎‎‎‏‏‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‎‎‎‎‏‏‎‎‎‎‏‏‏‎How it works‎‏‎‎‏‎"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‏‎‎‎‏‎‎‏‎‎‏‏‏‏‎‏‎‏‎‏‎‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‏‎‏‎‏‏‏‎‏‎‏‎‏‏‎‏‎Pending...‎‏‎‎‏‎"</string>
+    <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‎‏‎‏‏‎‏‎‏‏‎‎‎‎‏‏‎‎‎‏‎‎‏‏‎‏‎‏‎‏‏‎‎‎‏‏‎‎‏‏‏‏‏‎‎‏‏‏‏‎‏‎‏‎Set up Fingerprint Unlock again‎‏‎‎‏‎"</string>
+    <string name="fingerprint_dangling_notification_msg_1" msgid="6261149111900787302">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‏‎‎‏‎‎‎‎‎‏‎‎‎‏‏‏‏‏‎‎‎‏‏‎‏‏‏‏‎‎‏‎‎‏‏‎‎‏‎‏‎‏‎‎‏‎‎‏‏‎‎‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="FINGERPRINT">%s</xliff:g>‎‏‎‎‏‏‏‎ wasn\'t working well and was deleted to improve performance‎‏‎‎‏‎"</string>
+    <string name="fingerprint_dangling_notification_msg_2" msgid="7688302770424064884">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‎‏‎‎‏‎‏‎‏‏‎‏‎‏‎‎‎‏‎‏‎‏‎‎‏‎‎‏‏‎‎‎‏‏‏‎‎‏‎‏‎‏‏‎‏‏‏‎‏‎‎‎‎‏‎‎‏‏‎<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>‎‏‎‎‏‏‏‎ and ‎‏‎‎‏‏‎<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>‎‏‎‎‏‏‏‎ weren\'t working well and were deleted to improve performance‎‏‎‎‏‎"</string>
+    <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‎‏‏‎‏‏‎‏‏‏‏‏‎‎‎‎‏‏‎‎‎‏‎‎‎‏‎‎‎‏‎‏‎‏‎‏‏‎‎‎‎‎‎‎‏‎‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="FINGERPRINT">%s</xliff:g>‎‏‎‎‏‏‏‎ wasn\'t working well and was deleted. Set it up again to unlock your phone with fingerprint.‎‏‎‎‏‎"</string>
+    <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‎‏‎‎‏‏‎‎‎‏‎‎‏‏‏‎‏‎‎‏‎‎‎‏‎‎‏‎‏‎‏‏‏‏‎‏‎‎‎‏‎‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>‎‏‎‎‏‏‏‎ and ‎‏‎‎‏‏‎<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>‎‏‎‎‏‏‏‎ weren\'t working well and were deleted. Set them up again to unlock your phone with your fingerprint.‎‏‎‎‏‎"</string>
+    <string name="face_dangling_notification_title" msgid="947852541060975473">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‎‏‏‏‎‏‏‏‎‎‏‎‏‏‎‏‏‏‎‏‎‎‏‏‏‎‎‏‎‏‏‎‎‏‎‎‎‎‎‎‏‎‏‏‎‏‏‏‎‎‎‏‎Set up Face Unlock again‎‏‎‎‏‎"</string>
+    <string name="face_dangling_notification_msg" msgid="8806849376915541655">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎‎‎‎‎‏‏‎‏‏‎‏‏‎‏‏‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‏‏‏‎‎‏‏‏‎‏‎‎‏‎‏‏‏‎Your face model wasn\'t working well and was deleted. Set it up again to unlock your phone with face.‎‏‎‎‏‎"</string>
+    <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‎‎‏‎‏‏‎‏‎‎‏‎‎‎‏‏‏‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‎‏‏‎‏‎‏‏‏‏‎‎‏‏‏‎‏‎‎‏‎Set up‎‏‎‎‏‎"</string>
+    <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‏‎‎‎‎‎‎‏‏‎‏‎‎‏‎‎‎‏‎‏‏‎‏‏‎‎‏‏‎‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‏‏‎‎‎‏‏‎Not now‎‏‎‎‏‎"</string>
 </resources>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index dd90f03..8717640 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -156,7 +156,7 @@
     <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: no se ha remitido"</string>
     <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Seguridad de redes móviles"</string>
     <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Encriptación, notificaciones para redes no encriptadas"</string>
-    <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Se accedió al ID de dispositivo"</string>
+    <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Se accedió al ID del dispositivo"</string>
     <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"A la(s) <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, una red cercana registró el ID único de tu dispositivo (IMSI o IMEI) mientras se usaba tu SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string>
     <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"A la(s) <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, una red cercana registró el ID único de tu dispositivo (IMSI o IMEI) mientras se usaba tu SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nEsto significa que se registró tu ubicación, actividad o identidad. Esta es una práctica común, pero podría significar un problema para personas preocupadas por su privacidad."</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Se conectó a la red encriptada de <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
@@ -2145,10 +2145,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificación de información del modo de Rutinas"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Ahorro de batería activado"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reduciendo el uso de la batería para extender su duración"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"El Ahorro de batería está activado"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Se activó el Ahorro de batería para extender la duración de batería"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Ahorro de batería"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Se desactivó el Ahorro de batería"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"El teléfono tiene suficiente carga. Ya no se restringen las funciones."</string>
@@ -2404,14 +2402,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Trabajo 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Probar"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Compartido"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Perfil de trabajo"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espacio privado"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clon"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Compartido"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Se ocultó contenido sensible de la notificación"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Se ocultó el contenido de la app durante el uso compartido de la pantalla por motivos de seguridad"</string>
@@ -2420,4 +2414,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir Mensajes"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cómo funciona"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendiente…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 3be0785..6549da2 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -158,7 +158,7 @@
     <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Cifrado, notificaciones sobre redes no cifradas"</string>
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Se ha accedido al ID del dispositivo"</string>
     <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"A las <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, una red cercana registró el ID único de tu dispositivo (IMSI o IMEI) mientras usabas la SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string>
-    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"A las <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, una red cercana registró el ID único de tu dispositivo (IMSI o IMEI) mientras usabas la SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nEsto significa que se ha registrado tu ubicación, actividad o identidad. Se trata de una práctica habitual, pero puede ser un problema para aquellos a quienes les preocupa su privacidad."</string>
+    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"A las <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, una red cercana registró el ID único de tu dispositivo (IMSI o IMEI) mientras usabas la SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nEsto significa que se ha registrado tu ubicación, actividad o identidad. Se trata de una práctica habitual, pero puede ser un problema para quienes les preocupa su privacidad."</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Conectado a la red cifrada <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Ahora, la conexión con la SIM de <xliff:g id="NETWORK_NAME">%1$s</xliff:g> es más segura"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Conectado a una red no cifrada"</string>
@@ -2145,10 +2145,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificación sobre el modo rutina"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Ahorro de batería activado"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reduciendo el uso de batería para prolongar su duración"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Ahorro de batería activado"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Se ha activado Ahorro de batería para prolongar la duración de la batería"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Ahorro de batería"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Ahorro de batería desactivado"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"El teléfono tiene suficiente batería. Las funciones ya no están restringidas."</string>
@@ -2416,4 +2414,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abre Mensajes"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cómo funciona"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendiente..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 1e1eb1c..92f89e3 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -154,7 +154,7 @@
     <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: pole suunatud"</string>
     <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: pole edastatud"</string>
     <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Mobiilsidevõrgu turve"</string>
-    <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Krüpteerimine, märguanded krüpteerimata võrkude jaoks"</string>
+    <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Krüpteerimine, märguanded krüpteerimata võrkude kohta"</string>
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Seadme ID-le on juurde pääsetud"</string>
     <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Kell <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> salvestas lähedal olev võrk võrgu <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM-i kasutamise ajal teie seadme kordumatu ID (IMSI või IMEI)"</string>
     <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Kell <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> salvestas lähedal olev võrk võrgu <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM-i kasutamise ajal teie seadme kordumatu ID (IMSI või IMEI).\n\nSee tähendab, et teie asukoht, tegevus või isik salvestati. See on levinud tava, kuid võib osutada probleemiks inimeste jaoks, kellele on privaatsus eriti oluline."</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Rutiinirežiimi teabe märguanne"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Akusäästja lülitati sisse"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Aku tööea pikendamiseks vähendatakse akukasutust"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Akusäästja on sisse lülitatud"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Akusäästja on sisse lülitatud, et pikendada aku tööiga"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Akusäästja"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Akusäästja on välja lülitatud"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefon on piisavalt laetud. Funktsioonid ei ole enam piiratud."</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Ava rakendus Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Tööpõhimõtted"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Ootel …"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index eccb4b1..2d4130e 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -467,9 +467,9 @@
     <string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Igorpen iraunkorrak egiteko baimena ematen dio aplikazioari. Igorpena amaitu ondoren ere igortzen jarraitzen dute igorpen iraunkorrek. Gehiegi erabiliz gero, Android TV gailua motel edo ezegonkor ibiliko da, memoria gehiago erabiliko delako."</string>
     <string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Igorpen iraunkorrak emateko baimena ematen dio aplikazioari; horiek igorpena amaitu ondoren mantentzen dira. Gehiegi erabiliz gero, telefonoa motel edo ezegonkor ibiliko da, memoria gehiago erabiliko delako."</string>
     <string name="permlab_readContacts" msgid="8776395111787429099">"irakurri kontaktuak"</string>
-    <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Tabletan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten tabletako kontuak ere atzitu ahalko ditu aplikazioak. Horrek barnean hartuko ditu instalatutako aplikazioak sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzake aplikazioak, eta baliteke asmo txarreko aplikazioek zuk jakin gabe partekatzea datu horiek."</string>
-    <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Android TV gailuan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten Android TV gailuko kontuak ere atzitu ahalko ditu aplikazioak. Horrek barnean hartuko ditu instalatutako aplikazioak sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzake aplikazioak, eta baliteke asmo txarreko aplikazioek zuk jakin gabe partekatzea datu horiek."</string>
-    <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Telefonoan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten telefonoko kontuak ere atzitu ahalko ditu aplikazioak. Horrek barnean hartuko ditu instalatutako aplikazioak sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzake aplikazioak, eta baliteke asmo txarreko aplikazioek zuk jakin gabe partekatzea datu horiek."</string>
+    <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Tabletan biltegiratutako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten tabletako kontuak ere atzitu ahalko ditu aplikazioak. Horrek barnean hartuko ditu instalatutako aplikazioak sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzake aplikazioak, eta baliteke asmo txarreko aplikazioek zuk jakin gabe partekatzea datu horiek."</string>
+    <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Android TV gailuan biltegiratutako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten Android TV gailuko kontuak ere atzitu ahalko ditu aplikazioak. Horrek barnean hartuko ditu instalatutako aplikazioak sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzake aplikazioak, eta baliteke asmo txarreko aplikazioek zuk jakin gabe partekatzea datu horiek."</string>
+    <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Telefonoan biltegiratutako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten telefonoko kontuak ere atzitu ahalko ditu aplikazioak. Horrek barnean hartuko ditu instalatutako aplikazioak sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzake aplikazioak, eta baliteke asmo txarreko aplikazioek zuk jakin gabe partekatzea datu horiek."</string>
     <string name="permlab_writeContacts" msgid="8919430536404830430">"aldatu kontaktuak"</string>
     <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Tabletan gordetako kontaktuei buruzko datuak aldatzeko baimena ematen dio aplikazioari. Baimen horrekin, aplikazioak kontaktuen datuak ezaba ditzake."</string>
     <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Android TV gailuan gordetako kontaktuei buruzko datuak aldatzeko baimena ematen dio aplikazioari. Baimen horrekin, aplikazioak kontaktuen datuak ezaba ditzake."</string>
@@ -485,9 +485,9 @@
     <string name="permlab_bodySensors_background" msgid="4912560779957760446">"Atzitu gorputz-sentsoreen datuak (adib., bihotz-maiztasunarenak) atzeko planoan"</string>
     <string name="permdesc_bodySensors_background" product="default" msgid="8870726027557749417">"Aplikazioa atzeko planoan egon bitartean, gorputz-sentsoreen datuak (besteak beste, bihotz-maiztasuna, tenperatura eta odolean dagoen oxigenoaren ehunekoa) erabiltzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_readCalendar" msgid="6408654259475396200">"irakurri egutegiko gertaerak eta xehetasunak"</string>
-    <string name="permdesc_readCalendar" product="tablet" msgid="515452384059803326">"Aplikazioak tabletan gordetako egutegiko gertaerak irakur ditzake eta egutegiko datuak parteka eta gorde ditzake."</string>
-    <string name="permdesc_readCalendar" product="tv" msgid="5811726712981647628">"Aplikazioak Android TV gailuan gordeta dituzun egutegiko gertaerak irakur ditzake, baita egutegiko datuak partekatu eta gorde ere."</string>
-    <string name="permdesc_readCalendar" product="default" msgid="9118823807655829957">"Aplikazioak telefonoan gordetako egutegiko gertaerak irakur ditzake eta egutegiko datuak parteka eta gorde ditzake."</string>
+    <string name="permdesc_readCalendar" product="tablet" msgid="515452384059803326">"Aplikazioak tabletan biltegiratutako egutegiko gertaerak irakur ditzake eta egutegiko datuak parteka eta gorde ditzake."</string>
+    <string name="permdesc_readCalendar" product="tv" msgid="5811726712981647628">"Aplikazioak Android TV gailuan biltegiratuta dituzun egutegiko gertaerak irakur ditzake, baita egutegiko datuak partekatu eta gorde ere."</string>
+    <string name="permdesc_readCalendar" product="default" msgid="9118823807655829957">"Aplikazioak telefonoan biltegiratutako egutegiko gertaerak irakur ditzake eta egutegiko datuak parteka eta gorde ditzake."</string>
     <string name="permlab_writeCalendar" msgid="6422137308329578076">"gehitu edo aldatu egutegiko gertaerak eta bidali mezu elektronikoak gonbidatuei jabeek jakin gabe"</string>
     <string name="permdesc_writeCalendar" product="tablet" msgid="8722230940717092850">"Tabletako gertaerak gehitzeko, kentzeko edo aldatzeko aukera du aplikazioak. Gainera, egutegien jabeenak diruditen mezuak bidal ditzake, eta gertaerak alda ditzake jabeei beraiei jakinarazi gabe."</string>
     <string name="permdesc_writeCalendar" product="tv" msgid="951246749004952706">"Android TV gailuan egutegiko gertaerak gehitzeko eta gehitutakoak kentzeko edo aldatzeko aukera dute aplikazioek. Gainera, egutegien jabeenak diruditen mezuak bidal ditzakete, edo gertaerak aldatu jabeei ezer esan gabe."</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Ohitura moduaren informazio-jakinarazpena"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Bateria-aurreztailea aktibatu da"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Bateria-erabilera murrizten hasi da haren iraupena luzatzeko"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Bateria-aurreztailea aktibatuta dago"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Bateria-aurreztailea aktibatuta dago, bateriaren iraupena luzatzeko"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Bateria-aurreztailea"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Desaktibatu egin da bateria-aurreztailea"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Behar adina bateria dauka telefonoak. Jada ez dago eginbiderik murriztuta."</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Lanekoa 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Probakoa"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Partekatua"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Laneko profila"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Eremu pribatua"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klona"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Partekatua"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Jakinarazpenaren kontuzko edukia ezkutatu da"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Aplikazioko edukia ezkutatu egin da pantaila partekatzeko eginbidetik, segurtasuna bermatzeko"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Ireki Mezuak"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Nola funtzionatzen du?"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Zain…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index f2ee5f1..07b2674 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -155,12 +155,12 @@
     <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: هدایت نشده"</string>
     <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"امنیت شبکه تلفن همراه"</string>
     <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"رمزگذاری، اعلان‌های شبکه‌های رمزگذاری‌نشده"</string>
-    <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"شناسه دستگاه دردسترس قرار گرفته است"</string>
+    <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"شناسه دستگاه  مورددسترس قرار گرفت"</string>
     <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"‏ساعت <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>، یکی از شبکه‌های اطراف شناسه یکتای دستگاهتان (IMSI یا IMEI) را هنگام استفاده از سیم‌کارت <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> ضبط کرده است"</string>
     <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"‏ساعت <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>، یکی از شبکه‌های اطراف شناسه یکتای دستگاهتان (IMSI یا IMEI) را هنگام استفاده از سیم‌کارت <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> ضبط کرده است.\n\nاین یعنی مکان، فعالیت، یا هویت شما ثبت شده است. این رویکرد عادی است اما ممکن است برای افرادی که نگران حریم خصوصی‌شان هستند مشکل‌ساز باشد."</string>
-    <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"به شبکه رمزگذاری‌شده <xliff:g id="NETWORK_NAME">%1$s</xliff:g> متصل شدید"</string>
+    <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"به شبکه رمزگذاری‌شده <xliff:g id="NETWORK_NAME">%1$s</xliff:g> متصل‌اید"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"اتصال سیم‌کارت <xliff:g id="NETWORK_NAME">%1$s</xliff:g> اکنون ایمن‌تر است"</string>
-    <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"به شبکه رمزگذاری‌نشده متصل شدید"</string>
+    <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"به شبکه رمزگذاری‌نشده متصل‌اید"</string>
     <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"درحال‌حاضر تماس‌ها، پیام‌ها، و داده‌ها هنگام استفاده از سیم‌کارت <xliff:g id="NETWORK_NAME">%1$s</xliff:g> آسیب‌پذیرتر هستند"</string>
     <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"درحال‌حاضر تماس‌ها، پیام‌ها، و داده‌ها هنگام استفاده از سیم‌کارت <xliff:g id="NETWORK_NAME">%1$s</xliff:g> آسیب‌پذیرتر هستند.\n\nوقتی اتصال شما دوباره رمزگذاری شود، اعلان دیگری دریافت خواهید کرد."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"تنظیمات امنیت شبکه تلفن همراه"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"اعلان اطلاعات حالت روال معمول"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"«بهینه‌سازی باتری» روشن شد"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"برای افزایش عمر باتری، مصرف باتری کاهش می‌یابد"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"«بهینه‌سازی باتری» روشن است"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"«بهینه‌سازی باتری» برای افزایش عمر باتری روشن شد"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"بهینه‌سازی باتری"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"«بهینه‌سازی باتری» خاموش شد"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"تلفن به‌اندازه کافی شارژ دارد. ویژگی‌ها دیگر محدود نمی‌شوند."</string>
@@ -2397,20 +2395,16 @@
     <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"صفحه‌کلیدهای فیزیکی پیکربندی شدند"</string>
     <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"برای مشاهده صفحه‌کلیدها ضربه بزنید"</string>
     <string name="profile_label_private" msgid="6463418670715290696">"خصوصی"</string>
-    <string name="profile_label_clone" msgid="769106052210954285">"مشابه‌سازی"</string>
+    <string name="profile_label_clone" msgid="769106052210954285">"همسانه‌سازی"</string>
     <string name="profile_label_work" msgid="3495359133038584618">"کار"</string>
     <string name="profile_label_work_2" msgid="4691533661598632135">"کار ۲"</string>
     <string name="profile_label_work_3" msgid="4834572253956798917">"کار ۳"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"آزمایش"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"عمومی"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"نمایه کاری"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"فضای خصوصی"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"همسانه‌سازی"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"همگانی"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"محتوای اعلان حساس پنهان شده است"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"به‌دلایل امنیتی، محتوای برنامه از دید هم‌رسانی صفحه‌نمایش پنهان شد"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"باز کردن «پیام‌ها»"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"روش کار"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"درحال تعلیق…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 053b665..e802443 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -160,7 +160,7 @@
     <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Klo <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> lähellä oleva verkko tallensi laitteesi yksilöllisen tunnuksen (IMSI tai IMEI), kun käytössä oli SIM-kortti, jonka <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> tarjoaa.\n\nTämä tarkoittaa, että sijaintisi, toimintasi tai henkilöllisyytesi on tallennettu. Tämä on tavallista mutta voi huolestuttaa ihmisiä, jotka ovat tarkkoja yksityisyydestään."</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Yhdistetty salattuun verkkoon <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"SIM-yhteys, jonka <xliff:g id="NETWORK_NAME">%1$s</xliff:g> tarjoaa, on nyt turvallisempi"</string>
-    <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Yhdistetty salattuun verkkoon"</string>
+    <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Yhdistetty salaamattomaan verkkoon"</string>
     <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Puhelut, viestit ja data ovat haavoittuvaisempia, kun käytössä on SIM-kortti, jonka <xliff:g id="NETWORK_NAME">%1$s</xliff:g> tarjoaa"</string>
     <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Puhelut, viestit ja data ovat haavoittuvaisempia, kun käytössä on SIM-kortti, jonka <xliff:g id="NETWORK_NAME">%1$s</xliff:g> tarjoaa.\n\nKun yhteys on taas salattu, saat uuden ilmoituksen."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Mobiiliverkkoa koskevat turvallisuusasetukset"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Ohjelmatilan tietoilmoitus"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Virransäästö päällä"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Akun käyttöä rajoitetaan akunkeston pidentämiseksi"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Virransäästö on päällä"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Virransäästö on laitettu päälle akunkeston pidentämiseksi"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Virransäästö"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Virransäästö laitettiin pois päältä"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Puhelimessa on tarpeeksi virtaa. Ominaisuuksia ei enää rajoiteta."</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Työ 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Testi"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Jaettu"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Työprofiili"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Yksityinen tila"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klooni"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Yhteinen"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Arkaluontoisen ilmoituksen sisältö piilotettu"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Sovelluksen sisältö piilotettu näytön jakamiselta turvallisuussyistä"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Avaa Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Näin se toimii"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Odottaa…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index de1343c..bf70c3c 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -162,8 +162,8 @@
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Connecté à un réseau chiffré <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"La connexion à la carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> est maintenant plus sûre"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Connecté à un réseau non chiffré"</string>
-    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Les appels, les messages et les données sont actuellement plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
-    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Les appels, les messages et les données sont actuellement plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nUne fois que votre connexion est à nouveau chiffrée, vous recevez une nouvelle notification."</string>
+    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Les appels, messages et données sont plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
+    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Les appels, les messages et les données sont actuellement plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nLorsque votre connexion sera à nouveau chiffrée, vous recevrez une nouvelle notification."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Paramètres de sécurité du réseau cellulaire"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"En savoir plus"</string>
     <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"OK"</string>
@@ -252,7 +252,7 @@
     <string name="reboot_safemode_title" msgid="5853949122655346734">"Redémarrer en mode sans échec"</string>
     <string name="reboot_safemode_confirm" msgid="1658357874737219624">"Voulez-vous redémarrer en mode sans échec? Cette opération aura pour effet de désactiver toutes les applications tierces que vous avez installées. Elles seront réactivées au prochain redémarrage."</string>
     <string name="recent_tasks_title" msgid="8183172372995396653">"Récents"</string>
-    <string name="no_recent_tasks" msgid="9063946524312275906">"Aucune application récente"</string>
+    <string name="no_recent_tasks" msgid="9063946524312275906">"Aucune appli récente"</string>
     <string name="global_actions" product="tablet" msgid="4412132498517933867">"Options de la tablette"</string>
     <string name="global_actions" product="tv" msgid="3871763739487450369">"Options d\'Android TV"</string>
     <string name="global_actions" product="default" msgid="6410072189971495460">"Options du téléphone"</string>
@@ -2145,10 +2145,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notification d\'information du mode Routine"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Économiseur de pile activé"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Réduction de l\'utilisation de la pile pour en prolonger l\'autonomie"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"La fonctionnalité Économiseur de pile est activée"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"La fonctionnalité Économiseur de pile est activée pour prolonger l\'autonomie de la pile"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Économiseur de pile"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Le mode Économiseur de pile est désactivé"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Le téléphone est suffisamment chargé. Ces fonctionnalités ne sont plus restreintes."</string>
@@ -2404,14 +2402,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Professionnel 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Commun"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profil professionnel"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espace privé"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Commun"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Le contenu confidentiel de la notification est masqué"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Le contenu de l\'application est masqué du Partage d\'écran par mesure de sécurité"</string>
@@ -2420,4 +2414,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Ouvrir Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Fonctionnement"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"En attente…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 31ee078..5727224 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -163,7 +163,7 @@
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"La connexion à la carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> est désormais plus sécurisée"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Connecté à un réseau non chiffré"</string>
     <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Les appels, les messages et les données sont actuellement plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
-    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Les appels, les messages et les données sont actuellement plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nLorsque votre connexion est à nouveau chiffrée, vous recevez une nouvelle notification."</string>
+    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Les appels, les messages et les données sont actuellement plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nLorsque votre connexion sera à nouveau chiffrée, vous recevrez une nouvelle notification."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Paramètres de sécurité du réseau mobile"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"En savoir plus"</string>
     <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"OK"</string>
@@ -2145,10 +2145,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notification d\'information du mode Routine"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Économiseur de batterie activé"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Réduction de l\'utilisation de la batterie pour prolonger son autonomie"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Économiseur de batterie activé"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"L\'économiseur de batterie est activé pour prolonger l\'autonomie de la batterie"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Économiseur de batterie"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Économiseur de batterie désactivé"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Téléphone suffisamment chargé. Les fonctionnalités ne sont plus restreintes."</string>
@@ -2406,7 +2404,7 @@
     <string name="profile_label_communal" msgid="8743921499944800427">"Commun"</string>
     <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profil professionnel"</string>
     <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espace privé"</string>
-    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Cloner"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string>
     <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Commun"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Le contenu sensible de la notification a été masqué"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
@@ -2416,4 +2414,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Ouvrir Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Fonctionnement"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"En attente…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index f3b0a18..663ef9a 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -158,9 +158,9 @@
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Acceso ao código do dispositivo"</string>
     <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Á/s <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, unha rede próxima rexistrou o código exclusivo (IMSI ou IMEI) do teu dispositivo mentres se usaba a túa SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string>
     <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Á/s <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, unha rede próxima rexistrou o código exclusivo (IMSI ou IMEI) do teu dispositivo mentres se usaba a túa SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nIsto significa que se rexistrou a túa localización, actividade ou identidade. Aínda que se trata dunha práctica común, pode supoñer un problema para as persoas ás que lles preocupe a súa privacidade."</string>
-    <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Conexión á rede encriptada <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
+    <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Conectácheste á rede encriptada <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"A conexión coa SIM de <xliff:g id="NETWORK_NAME">%1$s</xliff:g> agora é máis segura"</string>
-    <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Conexión a unha rede non encriptada"</string>
+    <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Conectácheste a unha rede non encriptada"</string>
     <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Cando usas a SIM de <xliff:g id="NETWORK_NAME">%1$s</xliff:g>, as chamadas, mensaxes e datos son máis vulnerables"</string>
     <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Cando usas a SIM de <xliff:g id="NETWORK_NAME">%1$s</xliff:g>, as chamadas, mensaxes e datos son máis vulnerables.\n\nRecibirás outra notificación cando se volva encriptar a túa conexión."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Configuración de seguranza das redes de telefonía móbil"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificación da información do modo de rutina"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Activouse Aforro de batería"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Estase limitando o uso da batería para aumentar a súa duración"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"A función Aforro de batería está activada"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Activouse a función Aforro de batería para prolongar a duración"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Aforro de batería"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Desactivouse a función Aforro de batería."</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"O teléfono non ten suficiente batería. Xa non se restrinxirán as funcións."</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir Mensaxes"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona?"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendente..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index e4fe845..dc42537 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"રૂટિન મોડની માહિતીનું નોટિફિકેશન"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"બૅટરી સેવરની સુવિધા ચાલુ કરી છે"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"બૅટરીની આવરદા વધારવા માટે બૅટરીનો વપરાશ ઘટાડી રહ્યાં છીએ"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"બૅટરી સેવર ચાલુ છે"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"બૅટરીની આવરદા વધારવા માટે, બૅટરી સેવર ચાલુ કરવામાં આવ્યું છે"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"બૅટરી સેવર"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"બૅટરી સેવર બંધ કર્યું"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"ફોનમાં પૂરતો ચાર્જ છે. સુવિધાઓ હવે મર્યાદિત નથી."</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ખોલો"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"તેની કામ કરવાની રીત"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"બાકી..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 716fabf..25f6ca1 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -154,15 +154,15 @@
     <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: अग्रेषित नहीं किया गया"</string>
     <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: अग्रेषित नहीं किया गया"</string>
     <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"मोबाइल नेटवर्क की सुरक्षा"</string>
-    <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"उन नेटवर्क के लिए सुरक्षा से जुड़ी सूचनाएं जो सुरक्षित नहीं हैं"</string>
+    <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"एन्क्रिप्शन, असुरक्षित नेटवर्क के लिए सूचनाएं"</string>
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"डिवाइस आईडी को ऐक्सेस किया गया"</string>
-    <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"आपके <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> सिम का इस्तेमाल करके, आस-पास के नेटवर्क ने <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> आपके डिवाइस का यूनीक आईडी (IMSI या IMEI) रिकॉर्ड किया"</string>
-    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"आपका <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> सिम इस्तेमाल करके, आस-पास के नेटवर्क ने <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> आपके डिवाइस का यूनीक आईडी (IMSI या IMEI) रिकॉर्ड किया.\n\nइसका मतलब है कि आपकी जगह की जानकारी, गतिविधि या निजी जानकारी को लॉग इन किया गया है. यह सामान्य तरीका है. हालांकि, यह उन लोगों के लिए समस्या की वजह हो सकता है जिन्हें अपनी निजी जानकारी को लेकर चिंता रहती है."</string>
+    <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"आपके <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> सिम के ज़रिए, आस-पास के नेटवर्क ने <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> पर आपके डिवाइस का यूनीक आईडी (IMSI या IMEI) रिकॉर्ड किया"</string>
+    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"आपके <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> सिम के ज़रिए, आस-पास के नेटवर्क ने <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> पर आपके डिवाइस का यूनीक आईडी (IMSI या IMEI) रिकॉर्ड किया.\n\nइसका मतलब है कि आपकी जगह की जानकारी, गतिविधि या निजी जानकारी को लॉग किया गया है. यह आम बात है. हालांकि, यह उन लोगों के लिए समस्या की वजह हो सकता है जिन्हें अपनी निजी जानकारी को लेकर चिंता रहती है."</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"एन्क्रिप्ट यानी सुरक्षित नेटवर्क <xliff:g id="NETWORK_NAME">%1$s</xliff:g> से कनेक्ट किया गया"</string>
-    <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"अब <xliff:g id="NETWORK_NAME">%1$s</xliff:g> सिम का कनेक्शन ज़्यादा सुरक्षित है"</string>
+    <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"अब <xliff:g id="NETWORK_NAME">%1$s</xliff:g> सिम का कनेक्शन ज़्यादा सुरक्षित हो गया है"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"ऐसे नेटवर्क से कनेक्ट किया गया जो सुरक्षित नहीं है"</string>
-    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> सिम का इस्तेमाल करने के दौरान, कॉल, मैसेज, और डेटा को ऐक्सेस किए जाने का खतरा हो सकता है"</string>
-    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> सिम का इस्तेमाल करने के दौरान, कॉल, मैसेज, और डेटा को ऐक्सेस किए जाने का खतरा हो सकता है.\n\nजब आपका कनेक्शन फिर से एन्क्रिप्ट यानी सुरक्षित हो जाएगा, तब आपको दोबारा सूचना भेजी जाएगी."</string>
+    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> सिम का इस्तेमाल करने पर, कॉल, मैसेज, और डेटा ऐक्सेस किए जाने का खतरा हो सकता है"</string>
+    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> सिम का इस्तेमाल करने पर, कॉल, मैसेज, और डेटा ऐक्सेस किए जाने का खतरा हो सकता है.\n\nजब आपका कनेक्शन फिर से एन्क्रिप्ट यानी सुरक्षित हो जाएगा, तब आपको नई सूचना भेजी जाएगी."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"मोबाइल नेटवर्क की सुरक्षा से जुड़ी सेटिंग"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"ज़्यादा जानें"</string>
     <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"ठीक है"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"रूटीन मोड जानकारी की सूचना"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"बैटरी सेवर चालू है"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"इससे बैटरी कम खर्च होती है और बैटरी लाइफ़ बढ़ती है"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"बैटरी सेवर चालू है"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"बैटरी लाइफ़ बढ़ाने के लिए बैटरी सेवर चालू किया गया है"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"बैटरी सेवर"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"बैटरी सेवर बंद कर दिया गया है"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"फ़ोन में काफ़ी बैटरी बची है. सुविधाओं पर अब पाबंदी नहीं है."</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"ऑफ़िस 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"टेस्ट"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"कम्यूनिटी"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"वर्क प्रोफ़ाइल"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"प्राइवेट स्पेस"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"क्लोन"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"कम्यूनिटी"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"संवेदनशील जानकारी वाली सूचना का कॉन्टेंट छिपा है"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"स्क्रीन शेयर करने के दौरान सुरक्षा के लिए, ऐप्लिकेशन का कॉन्टेंट छिपाया गया"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ऐप्लिकेशन खोलें"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"यह सेटिंग कैसे काम करती है"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"प्रोसेस जारी है..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 9c11027..117d4e5 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -2145,10 +2145,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Obavještavanje o informacijama u Rutinskom načinu rada"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Uključena je štednja baterije"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Smanjuje se potrošnja baterije radi produženja njezinog trajanja"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Štednja baterije je uključena"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Štednja baterije je uključena da bi se produljilo trajanje baterije"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Štednja baterije"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Isključena je Štednja baterije"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Baterija mobilnog telefona dovoljno je napunjena. Značajke više nisu ograničene."</string>
@@ -2404,14 +2402,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Posao 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Zajedničko"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Radni profil"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privatni prostor"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Zajedničko"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Skriven je osjetljiv sadržaj obavijesti"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Sadržaj aplikacije sakriven je od dijeljenja zaslona radi sigurnosti"</string>
@@ -2420,4 +2414,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvori Poruke"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kako to funkcionira"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Na čekanju..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 1e9cedf..de8fa84 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Információs értesítés a rutinmódról"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Akkumulátorkímélő mód bekapcsolva"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Akkuhasználat csökkentése a hosszabb akkumulátor-élettartam érdekében"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Akkumulátorkímélő mód bekapcsolva"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Az akkumulátor-üzemidő meghosszabbítása érdekében bekapcsolódott az Akkumulátorkímélő mód"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Akkumulátorkímélő mód"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Akkumulátorkímélő mód kikapcsolva"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"A telefon töltöttsége elegendő. A funkciók használata már nincs korlátozva."</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"A Messages megnyitása"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Hogyan működik?"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Függőben…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 96b943b..d379934 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -153,9 +153,9 @@
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> վայրկյանից"</string>
     <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. Չի վերահասցեավորվել"</string>
     <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. Չի վերահասցեավորվել"</string>
-    <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Ցանցային անվտանգություն"</string>
+    <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Բջջային ցանցի անվտանգություն"</string>
     <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Գաղտնագրում, ծանուցումներ չգաղտնագրված ցանցերի համար"</string>
-    <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Սարքի նույնացույցիչը հասանելի է դարձել"</string>
+    <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Սարքի նույնացուցիչը հասանելի է դարձել"</string>
     <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Ժամը <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>-ին մոտակա ցանցը գրանցել է ձեր սարքի եզակի նույնացուցիչը (IMSI-ը կամ IMEI-ը) <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-ի ձեր SIM քարտի օգտագործման ժամանակ"</string>
     <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Ժամը <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>-ին մոտակա ցանցը գրանցել է ձեր սարքի եզակի նույնացուցիչը (IMSI-ը կամ IMEI-ը) <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-ի ձեր SIM քարտի օգտագործման ժամանակ։\n\nԴա նշանակում է, որ ձեր տեղադրությունը, գործողությունները կամ անձը նույնականացնող տվյալները գրանցվել են։ Սա սովորական գործելակերպ է, սակայն կարող է խնդիր լինել այն մարդկանց համար, որոնք մտահոգված են իրենց գաղտնիությամբ։"</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Հեռախոսը միացավ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> գաղտնագրված ցանցին"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Ծանուցում լիցքավորման մասին"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Մարտկոցի տնտեսումը միացվել է"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Մարտկոցի օգտագործումը նվազեցվել է դրա աշխատաժամանակը երկարացնելու համար"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Մարտկոցի տնտեսումը միացված է"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Մարտկոցի տնտեսումը միացվել է՝ մարտկոցի աշխատաժամանակը երկարացնելու համար"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Մարտկոցի տնտեսում"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Մարտկոցի տնտեսումն անջատված է"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Հեռախոսի լիցքը բավարար է։ Գործառույթներն այլևս չեն սահմանափակվում։"</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Բացել Messages-ը"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ինչպես է դա աշխատում"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Առկախ է…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index e3a93c8..e6634d2 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -157,12 +157,12 @@
     <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Enkripsi, notifikasi untuk jaringan yang tidak terenkripsi"</string>
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"ID perangkat diakses"</string>
     <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Pada <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, jaringan di sekitar merekam ID unik perangkat Anda (IMSI atau IMEI) saat menggunakan kartu SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> Anda"</string>
-    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Pada <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, jaringan di sekitar merekam ID unik perangkat Anda (IMSI atau IMEI) saat menggunakan kartu SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> Anda.\n\nHal ini berarti lokasi, aktifitas, atau identitas Anda telah dicatat dalam log. Tindakan ini adalah praktik umum tetapi dapat menjadi masalah bagi orang yang mengkhawatirkan privasi."</string>
+    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Pada <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, jaringan di sekitar merekam ID unik perangkat Anda (IMSI atau IMEI) saat menggunakan kartu SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> Anda.\n\nHal ini berarti lokasi, aktivitas, atau identitas Anda telah dicatat dalam log. Tindakan ini adalah praktik umum tetapi dapat menjadi masalah bagi orang yang mengkhawatirkan privasi."</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Terhubung ke jaringan yang terenkripsi <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Koneksi kartu SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> kini lebih aman"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Terhubung ke jaringan yang tidak terenkripsi"</string>
     <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Panggilan, pesan, dan data saat ini lebih rentan saat menggunakan kartu SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> Anda"</string>
-    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Panggilan, pesan, dan data saat ini lebih rentan saat menggunakan kartu SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> Anda.\n\nKetika koneksi Anda terenkripsi lagi, Anda akan menerima notifikasi lainnya."</string>
+    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Panggilan, pesan, dan data saat ini lebih rentan saat menggunakan kartu SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> Anda.\n\nKetika koneksi Anda terenkripsi lagi, Anda akan kembali menerima notifikasi."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Setelan keamanan jaringan seluler"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Pelajari lebih lanjut"</string>
     <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Oke"</string>
@@ -639,7 +639,7 @@
     <string name="permdesc_mediaLocation" msgid="597912899423578138">"Mengizinkan aplikasi untuk membaca lokasi dari koleksi media Anda."</string>
     <string name="biometric_app_setting_name" msgid="3339209978734534457">"Gunakan biometrik"</string>
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Gunakan biometrik atau kunci layar"</string>
-    <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verifikasi bahwa ini memang Anda"</string>
+    <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verifikasi diri Anda"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Gunakan biometrik untuk melanjutkan"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Gunakan biometrik atau kunci layar untuk melanjutkan"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biometrik tidak tersedia"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notifikasi info Mode Rutinitas"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Penghemat Baterai diaktifkan"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Mengurangi penggunaan baterai untuk memperpanjang masa pakai baterai"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Penghemat Baterai aktif"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Penghemat Baterai diaktifkan untuk memperpanjang daya tahan baterai"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Penghemat Baterai"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Penghemat Baterai dinonaktifkan"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Baterai ponsel cukup terisi. Fitur tidak lagi dibatasi."</string>
@@ -2223,7 +2221,7 @@
     <string name="miniresolver_call_information" msgid="6739417525304184083">"Organisasi Anda hanya mengizinkan menelepon dari aplikasi kerja"</string>
     <string name="miniresolver_sms_information" msgid="4311292661329483088">"Organisasi Anda hanya mengizinkan pengiriman pesan dari aplikasi kerja"</string>
     <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Anda hanya dapat melakukan panggilan telepon dari aplikasi Telepon pribadi. Panggilan yang dilakukan dengan aplikasi Telepon pribadi akan ditambahkan ke histori panggilan pribadi."</string>
-    <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Anda hanya dapat mengirim pesan SMS dari aplikasi Message pribadi."</string>
+    <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Anda hanya dapat mengirim pesan SMS dari aplikasi Pesan pribadi."</string>
     <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Gunakan browser pribadi"</string>
     <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Gunakan browser kerja"</string>
     <string name="miniresolver_call" msgid="6386870060423480765">"Telepon"</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Kerja 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Pengujian"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Umum"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profil kerja"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Ruang privasi"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Umum"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Konten notifikasi sensitif disembunyikan"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Konten aplikasi disembunyikan dari berbagi layar untuk alasan keamanan"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Buka Message"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cara kerjanya"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Tertunda..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 78cc145..f77a18a 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Upplýsingatilkynning aðgerðastillingar"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Kveikt á rafhlöðusparnaði"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Dregur úr rafhlöðunotkun til að auka endingu rafhlöðunnar"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Kveikt er á rafhlöðusparnaði"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Kveikt er á rafhlöðusparnaði til að lengja rafhlöðuendingu"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Rafhlöðusparnaður"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Slökkt á rafhlöðusparnaði"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Síminn er með næga hleðslu. Eiginleikar eru ekki lengur takmarkaðir."</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Opna Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Svona virkar þetta"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Í bið…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 057f037..95ccad5 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -2145,10 +2145,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notifica di informazioni sulla modalità Routine"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Risparmio energetico attivato"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Riduzione dell\'utilizzo di batteria per estenderne la durata"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Risparmio energetico attivo"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Risparmio energetico è attivo per aumentare la durata della batteria"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Risparmio energetico"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Risparmio energetico disattivato"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Il telefono ha carica sufficiente. Funzionalità non più limitate."</string>
@@ -2404,14 +2402,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Lavoro 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Condiviso"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profilo di lavoro"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Spazio privato"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Condiviso"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Contenuti sensibili della notifica nascosti"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Contenuti dell\'app nascosti dalla condivisione schermo per sicurezza"</string>
@@ -2420,4 +2414,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Apri Messaggi"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Come funziona"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"In attesa…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 2525b08..fee437a 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -162,9 +162,9 @@
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"יש חיבור לרשת המוצפנת <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"‏החיבור של כרטיס ה-SIM של <xliff:g id="NETWORK_NAME">%1$s</xliff:g> מאובטח יותר עכשיו"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"יש חיבור לרשת לא מוצפנת"</string>
-    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"‏השיחות, ההודעות והנתונים שלך פגיעים יותר עכשיו בזמן השימוש בכרטיס ה-SIM של <xliff:g id="NETWORK_NAME">%1$s</xliff:g> שלך"</string>
-    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"‏שיחות, הודעות ונתונים פגיעים יותר עכשיו בזמן השימוש בכרטיס ה-SIM של <xliff:g id="NETWORK_NAME">%1$s</xliff:g> שלך.\n\nכשהחיבור שלך יוצפן שוב, תישלח אליך התראה נוספת."</string>
-    <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"הגדרות אבטחה של רשת סלולרית"</string>
+    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"‏השיחות, ההודעות והנתונים שלך פגיעים יותר עכשיו בזמן השימוש בכרטיס ה-SIM של <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
+    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"‏השיחות, ההודעות והנתונים שלך פגיעים יותר עכשיו בזמן השימוש בכרטיס ה-SIM של <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nכשהחיבור שלך יוצפן שוב, תישלח אליך התראה נוספת."</string>
+    <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"הגדרות אבטחה של הרשת הסלולרית"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"מידע נוסף"</string>
     <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"הבנתי"</string>
     <string name="fcComplete" msgid="1080909484660507044">"קוד תכונה הושלם."</string>
@@ -2145,10 +2145,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"התראת מידע לגבי מצב שגרתי"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"התכונה \'חיסכון בסוללה\' הופעלה"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"הפחתת השימוש בסוללה תאריך את חיי הסוללה"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"החיסכון בסוללה מופעל"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"מצב \'חיסכון בסוללה\' מופעל כדי להאריך את חיי הסוללה"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"חיסכון בסוללה"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"מצב \'חיסכון בסוללה\' כבוי"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"הטלפון טעון מספיק. התכונות כבר לא מוגבלות."</string>
@@ -2404,14 +2402,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"פרופיל עבודה 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"בדיקה"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"שיתופי"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"פרופיל העבודה"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"המרחב הפרטי"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"שכפול"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"שיתופי"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"יש תוכן רגיש בהתראה שהוסתר"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"תוכן האפליקציה מוסתר משיתוף המסך מטעמי אבטחה"</string>
@@ -2420,4 +2414,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"‏לפתיחת Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"איך זה עובד"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"בהמתנה..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 5c96e97..726db1c 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -154,7 +154,7 @@
     <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:転送できません"</string>
     <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:転送できません"</string>
     <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"モバイル ネットワーク セキュリティ"</string>
-    <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"暗号化(ネットワークが暗号化されていない場合に通知)"</string>
+    <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"暗号化、暗号化されていないネットワークに関する通知"</string>
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"デバイス ID へのアクセスが発生しました"</string>
     <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>、<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> の SIM の使用中に付近のネットワークでお使いのデバイスの一意の ID(IMSI または IMEI)が記録されました"</string>
     <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>、<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> の SIM の使用中に付近のネットワークでお使いのデバイスの一意の ID(IMSI または IMEI)が記録されました。\n\nつまり、あなたの位置情報、アクティビティ、身元などが記録されことになります。これはよくある事象ですが、プライバシーに不安を持たれている人にとっては問題になる可能性があります。"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"ルーティン モード情報の通知"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"バッテリー セーバーが ON になりました"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"バッテリー使用量を減らし、バッテリー駆動時間を延ばします"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"バッテリー セーバーが ON になっています"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"バッテリーを長持ちさせるためにバッテリー セーバーが ON になっています"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"バッテリー セーバー"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"バッテリー セーバーが OFF になりました"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"スマートフォンが十分に充電されました。機能は制限されなくなりました。"</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"仕事用 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"テスト"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"共用"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"仕事用プロファイル"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"プライベート スペース"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"複製"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"共用"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"プライベートな通知内容は表示されません"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"セキュリティ上、画面共有ではアプリの内容は非表示となります"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"メッセージ アプリを開く"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"仕組み"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"保留中..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 2f7a81b..bdf6c48 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"რუტინის რეჟიმის საინფორმაციო შეტყობინება"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"ბატარეის დამზოგველი ჩართულია"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"ბატარეის მოხმარების შემცირება ბატარეის მუშაობის გახანგრძლივების მიზნით"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"ბატარეის დამზოგი ჩართულია"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"ბატარეის მუშაობის გასახანგრძლივებლად ჩართულია ბატარეის დამზოგი"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ბატარეის დამზოგი"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"ბატარეის დამზოგი გამორთულია"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"ტელეფონი საკმარისად არის დატენილი. ფუნქციები შეზღუდული აღარ არის."</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"სამსახური 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"სატესტო"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"საერთო"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"სამსახურის პროფილი"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"კერძო სივრცე"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"კლონის შექმნა"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"საერთო"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"სენსიტიური შეტყობინების კონტენტი დამალულია"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ეკრანის გაზიარებიდან აპის კონტენტი დამალულია უსაფრთხოების მიზნით"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages-ის გახსნა"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"მუშაობის პრინციპი"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"მომლოდინე..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 1b1f002..55cf92b 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -162,7 +162,7 @@
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Енді <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасымен қосылу қорғалған."</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Шифрланбаған желіге қосылды"</string>
     <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасын пайдаланған кезде, қазіргі уақытта қоңырауларға, хабарларға және деректерге қауіп төнеді."</string>
-    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасын пайдаланған кезде, қазіргі уақытта қоңырауларға, хабарларға және деректерге қауіп төнеді.\n\nБайланыс қайта шифрланғанда, тағы бір хабарландыру келеді."</string>
+    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасын пайдаланған кезде, қазір қоңырауларға, хабарларға және деректерге зиян тию қаупі жоғары.\n\nБайланыс қайта шифрланғанда, тағы бір хабарландыру келеді."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Мобильдік желінің қауіпсіздік параметрлері"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Толық ақпарат"</string>
     <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Түсінікті"</string>
@@ -1964,7 +1964,7 @@
     <string name="maximize_button_text" msgid="4258922519914732645">"Жазу"</string>
     <string name="close_button_text" msgid="10603510034455258">"Жабу"</string>
     <string name="notification_messaging_title_template" msgid="772857526770251989">"<xliff:g id="CONVERSATION_TITLE">%1$s</xliff:g>: <xliff:g id="SENDER_NAME">%2$s</xliff:g>"</string>
-    <string name="call_notification_answer_action" msgid="5999246836247132937">"Жауап"</string>
+    <string name="call_notification_answer_action" msgid="5999246836247132937">"Жауап беру"</string>
     <string name="call_notification_answer_video_action" msgid="2086030940195382249">"Бейне"</string>
     <string name="call_notification_decline_action" msgid="3700345945214000726">"Қабылдамау"</string>
     <string name="call_notification_hang_up_action" msgid="9130720590159188131">"Тұтқаны қою"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Режим туралы хабарландыру"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Батареяны үнемдеу режимі қосулы"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Батарея шығынын азайтсаңыз, батареяның жұмысы ұзарады."</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Батареяны үнемдеу режимі қосулы"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Батарея жұмысының ұзақтығын арттыру үшін батареяны үнемдеу режимі қосылған."</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Батареяны үнемдеу режимі"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Батареяны үнемдеу режимі өшірілді"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Телефонның заряды жеткілікті. Функцияларға енді шектеу қойылмайды."</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages қолданбасын ашу"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Бұл қалай орындалады?"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Дайын емес…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 6555b27..6a826db4 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -156,8 +156,8 @@
     <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"សុវត្ថិភាពបណ្ដាញទូរសព្ទចល័ត"</string>
     <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"ការ​អ៊ីនគ្រីប ការ​ជូនដំណឹងសម្រាប់បណ្ដាញដែលមិនបានអ៊ីនគ្រីប"</string>
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"បានចូលប្រើប្រាស់លេខសម្គាល់​ឧបករណ៍"</string>
-    <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"នៅម៉ោង <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> បណ្ដាញនៅជិតបានកត់ត្រាលេខសម្គាល់ពិសេស (IMSI ឬ IMEI) របស់ឧបករណ៍អ្នក ពេលកំពុងប្រើស៊ីម <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> របស់អ្នក"</string>
-    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"នៅម៉ោង <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> បណ្ដាញនៅជិតបានកត់ត្រាលេខសម្គាល់ពិសេស (IMSI ឬ IMEI) របស់ឧបករណ៍អ្នក ពេលកំពុងប្រើស៊ីម <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> របស់អ្នក។\n\nនេះមានន័យថា ទីតាំង សកម្មភាព ឬអត្តសញ្ញាណរបស់អ្នកត្រូវបានចុះកំណត់ហេតុ។ នេះគឺជាការអនុវត្តទូទៅ ប៉ុន្តែអាចនឹងមានបញ្ហាសម្រាប់អ្នកដែលមានកង្វល់ពាក់ព័ន្ធនឹងឯកជនភាព។"</string>
+    <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"នៅម៉ោង <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> បណ្ដាញនៅជិតបានកត់ត្រាលេខកូដសម្គាល់ខុសពីគេ (IMSI ឬ IMEI) របស់ឧបករណ៍អ្នក ពេលកំពុងប្រើស៊ីម <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> របស់អ្នក"</string>
+    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"នៅម៉ោង <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> បណ្ដាញនៅជិតបានកត់ត្រាលេខកូដសម្គាល់ខុសពីគេ (IMSI ឬ IMEI) របស់ឧបករណ៍អ្នក ពេលកំពុងប្រើស៊ីម <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> របស់អ្នក។\n\nនេះមានន័យថា ទីតាំង សកម្មភាព ឬអត្តសញ្ញាណរបស់អ្នកត្រូវបានកត់ត្រាទុក។ នេះគឺជាការអនុវត្តទូទៅ ប៉ុន្តែអាចនឹងមានបញ្ហាសម្រាប់អ្នកដែលមានកង្វល់ពាក់ព័ន្ធនឹងឯកជនភាព។"</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"បានភ្ជាប់ទៅបណ្ដាញដែលបានអ៊ីនគ្រីប <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"ការតភ្ជាប់ស៊ីម <xliff:g id="NETWORK_NAME">%1$s</xliff:g> កាន់តែមានសុវត្ថិភាពឥឡូវនេះ"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"បានភ្ជាប់ទៅបណ្ដាញដែលមិនបានអ៊ីនគ្រីប"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"ការ​ជូនដំណឹង​ព័ត៌មាន​របស់​មុខងារ​ទម្លាប់"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"បានបើកមុខងារ​សន្សំ​ថ្ម"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"ការកាត់បន្ថយការប្រើប្រាស់ថ្ម ដើម្បីបង្កើនកម្រិតថាមពលថ្ម"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"មុខងារ​សន្សំ​ថ្មត្រូវបានបើក"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"មុខងារ​សន្សំ​ថ្មត្រូវបានបើក ដើម្បីបង្កើនកម្រិតថាមពលថ្ម"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"មុខងារ​សន្សំ​ថ្ម"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"មុខងារ​សន្សំ​ថ្ម​ត្រូវបានបិទ"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"ទូរសព្ទ​មាន​កម្រិតថ្ម​គ្រប់គ្រាន់​។ មុខងារ​ផ្សេងៗ​មិន​ត្រូវបាន​រឹតបន្តឹងទៀត​ទេ។"</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"ការងារទី 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"ការធ្វើ​តេស្ត"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"ទូទៅ"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"កម្រងព័ត៌មានការងារ"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"លំហ​ឯកជន"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ក្លូន"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"ទូទៅ"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"បានលាក់ខ្លឹមសារជូនដំណឹងដែលមានលក្ខណៈរសើប"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"បានលាក់ខ្លឹមសារកម្មវិធីពីការបង្ហាញ​អេក្រង់ដើម្បីសុវត្ថិភាព"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"បើក​កម្មវិធី Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"របៀបដែលវាដំណើរការ"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"កំពុងរង់ចាំ..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 9d7d8c4..40d5138 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -156,12 +156,12 @@
     <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"ಮೊಬೈಲ್‌ ನೆಟ್‌ವರ್ಕ್‌ ಸೆಕ್ಯೂರಿಟಿ"</string>
     <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"ಎನ್‌ಕ್ರಿಪ್ಶನ್, ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡದ ನೆಟ್‌ವರ್ಕ್‌ಗಳಿಗೆ ನೋಟಿಫಿಕೇಶನ್‌ಗಳು"</string>
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"ಸಾಧನದ ID ಅನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲಾಗಿದೆ"</string>
-    <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> ಸಮಯದಲ್ಲಿ, ನಿಮ್ಮ ಸಾಧನದ ಅನನ್ಯ ID (IMSI ಅಥವಾ IMEI) ಅನ್ನು ನಿಮ್ಮ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM ಕಾರ್ಡ್ ಬಳಸುವಾಗ ಹತ್ತಿರದ ನೆಟ್‌ವರ್ಕ್‌ನಿಂದ ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗಿದೆ"</string>
+    <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>ಕ್ಕೆ, <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM ಬಳಸುವಾಗ ಸಾಧನದ ಅನನ್ಯ ID (IMSI ಅಥವಾ IMEI)ಯನ್ನು ಹತ್ತಿರದ ನೆಟ್‌ವರ್ಕ್‌ ರೆಕಾರ್ಡ್ ಮಾಡಿದೆ"</string>
     <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> ಸಮಯದಲ್ಲಿ, ನಿಮ್ಮ ಸಾಧನದ ಅನನ್ಯ ID (IMSI ಅಥವಾ IMEI) ಅನ್ನು ನಿಮ್ಮ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM ಕಾರ್ಡ್ ಬಳಸುವಾಗ ಹತ್ತಿರದ ನೆಟ್‌ವರ್ಕ್‌ನಿಂದ ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗಿದೆ.\n\nಇದರರ್ಥ ನಿಮ್ಮ ಸ್ಥಳ, ಚಟುವಟಿಕೆ ಅಥವಾ ಗುರುತನ್ನು ಲಾಗ್ ಮಾಡಲಾಗಿದೆ. ಇದು ಸಾಮಾನ್ಯ ವಿಧಾನವಾಗಿದೆ ಆದರೆ ಗೌಪ್ಯತೆಯ ಕುರಿತು ಕಾಳಜಿವಹಿಸುವವರಿಗೆ ಸಮಸ್ಯೆಯಾಗಬಹುದು."</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಿದ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ನೆಟ್‌ವರ್ಕ್‌ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM ಕನೆಕ್ಷನ್ ಈಗ ಹೆಚ್ಚು ಸುರಕ್ಷಿತವಾಗಿದೆ"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡದ ನೆಟ್‌ವರ್ಕ್‌ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string>
-    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"ನಿಮ್ಮ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM ಕಾರ್ಡ್ ಬಳಸುವಾಗ ಕರೆಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಡೇಟಾ ಪ್ರಸ್ತುತ ಹೆಚ್ಚು ಸೂಕ್ಷ್ಮವಾಗಿರುತ್ತದೆ."</string>
+    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"ನಿಮ್ಮ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM ಕಾರ್ಡ್ ಬಳಸುವಾಗ ಕರೆಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಡೇಟಾವನ್ನು ಬೇರೆಯವರು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಬಹುದಾದ ಅಪಾಯವಿರುತ್ತದೆ"</string>
     <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"ನಿಮ್ಮ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM ಕಾರ್ಡ್ ಬಳಸುವಾಗ ಕರೆಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಡೇಟಾ ಪ್ರಸ್ತುತ ಹೆಚ್ಚು ಸೂಕ್ಷ್ಮವಾಗಿರುತ್ತದೆ.\n\nನಿಮ್ಮ ಕನೆಕ್ಷನ್ ಅನ್ನು ಮತ್ತೆ ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಿದಾಗ, ನೀವು ಇನ್ನೊಂದು ನೋಟಿಫಿಕೇಶನ್ ಅನ್ನು ಪಡೆಯುತ್ತೀರಿ."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"ಮೊಬೈಲ್ ನೆಟ್‌ವರ್ಕ್ ಸೆಕ್ಯೂರಿಟಿ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"ದೈನಂದಿನ ಸ್ಥಿತಿಯ ಮಾಹಿತಿಯ ನೋಟಿಫಿಕೇಶನ್"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"ಬ್ಯಾಟರಿ ಸೇವರ್ ಅನ್ನು ಆನ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"ಬ್ಯಾಟರಿ ಬಾಳಿಕೆಯನ್ನು ವಿಸ್ತರಿಸಲು ಬ್ಯಾಟರಿ ಬಳಕೆಯನ್ನು ಕಡಿಮೆ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"ಬ್ಯಾಟರಿ ಸೇವರ್ ಆನ್ ಆಗಿದೆ"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"ಬ್ಯಾಟರಿ ಬಾಳಿಕೆಯನ್ನು ವಿಸ್ತರಿಸಲು ಬ್ಯಾಟರಿ ಸೇವರ್ ಅನ್ನು ಆನ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ಬ್ಯಾಟರಿ ಸೇವರ್"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"ಬ್ಯಾಟರಿ ಸೇವರ್ ಅನ್ನು ಆಫ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"ಫೋನ್‌ನಲ್ಲಿ ಸಾಕಷ್ಟು ಚಾರ್ಜ್ ಇದೆ. ಇನ್ನು ಮುಂದೆ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗುವುದಿಲ್ಲ."</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"ಕೆಲಸ 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"ಪರೀಕ್ಷೆ"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"ಸಮುದಾಯ"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"ಪ್ರೈವೆಟ್ ಸ್ಪೇಸ್"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ಕ್ಲೋನ್"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"ಸಮುದಾಯ"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"ಸೂಕ್ಷ್ಮ ನೋಟಿಫಿಕೇಶನ್ ಕಂಟೆಂಟ್ ಅನ್ನು ಮರೆಮಾಡಲಾಗಿದೆ"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ಭದ್ರತೆಗಾಗಿ ಸ್ಕ್ರೀನ್‌‌ ಹಂಚಿಕೊಳ್ಳುವಿಕೆಯಲ್ಲಿ ಆ್ಯಪ್ ಕಂಟೆಂಟ್‌ ಅನ್ನು ಮರೆಮಾಡಲಾಗಿದೆ"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ಅನ್ನು ತೆರೆಯಿರಿ"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ಇದು ಹೇಗೆ ಕೆಲಸ ಮಾಡುತ್ತದೆ"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"ಬಾಕಿ ಉಳಿದಿದೆ..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index e76ccc9..dd47628 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"루틴 모드 정보 알림"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"절전 모드 사용 설정됨"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"배터리 사용량을 줄여서 배터리 수명을 늘립니다."</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"절전 모드 사용 중"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"배터리 수명을 늘리기 위해 절전 모드가 사용 설정되었습니다"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"절전 모드"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"절전 모드가 사용 중지되었습니다"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"휴대전화의 배터리가 충분하므로 기능이 더 이상 제한되지 않습니다"</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"직장 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"테스트"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"공동"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"직장 프로필"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"비공개 스페이스"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"클론"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"공동"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"민감한 알림 콘텐츠 숨김"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"보안을 위해 화면 공유에서 앱 콘텐츠가 숨겨집니다."</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"메시지 열기"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"작동 방식"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"대기 중…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index f2469f2..bb29c36 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -156,13 +156,13 @@
     <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Мобилдик тармактын коопсуздугу"</string>
     <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Шифрлөө, шифрленбеген тармактар жөнүндө билдирмелер"</string>
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Түзмөктүн идентификатору колдонулду"</string>
-    <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM картасын колдонуп жатканда жакын жердеги тармакта түзмөгүңүздүн өзгөчө идентификатору (IMSI же IMEI) жазылды"</string>
-    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM картасын колдонуп жатканда жакын жердеги тармакта түзмөгүңүздүн өзгөчө идентификатору (IMSI же IMEI) жазылды.\n\nЖүргөн жериңиз, аракеттериңиз же өздүгүңүз тууралуу маалымат катталды. Бул адаттагы көрүнүш болсо да, купуялыгы жөнүндө тынчсызданган адамдарга маселе жаратышы мүмкүн."</string>
-    <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Шифрленген тармакка (<xliff:g id="NETWORK_NAME">%1$s</xliff:g>) туташты"</string>
+    <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Саат <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM картасы колдонулуп жатканда, жакын жердеги тармак түзмөгүңүздүн өзгөчө идентификаторун (IMSI же IMEI) жазып алды."</string>
+    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Саат <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM картасы колдонулуп жатканда, жакын жердеги тармак түзмөгүңүздүн өзгөчө идентификаторун (IMSI же IMEI) жазып алды.\n\nТактап айтканда, жүргөн жериңиз, аракеттериңиз же өздүгүңүз катталды. Бул адаттагы көрүнүш болсо да, купуялыгы жөнүндө тынчсызданган адамдарга маселе жаратышы мүмкүн."</string>
+    <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Шифрленген <xliff:g id="NETWORK_NAME">%1$s</xliff:g> тармагына туташты"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Эми <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасы менен туташуу коопсуз болуп калды"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Шифрленбеген тармакка туташты"</string>
     <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Учурда <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасын колдонсоңуз, чалууларга, билдирүүлөргө жана маалыматтарга коркунуч жаралышы мүмкүн"</string>
-    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Учурда <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасын колдонсоңуз, чалууларга, билдирүүлөргө жана маалыматтарга коркунуч жаралышы мүмкүн.\n\nТуташууңуз кайра шифрленгенде дагы бир билдирме аласыз."</string>
+    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Учурда <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасын колдонсоңуз, чалууларга, билдирүүлөргө жана маалыматтарга коркунуч жаралышы мүмкүн.\n\nБайланышыңыз кайра шифрленгенде дагы бир билдирме аласыз."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Мобилдик тармактын коопсуздук параметрлери"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Кеңири маалымат"</string>
     <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Түшүндүм"</string>
@@ -1334,7 +1334,7 @@
     <!-- no translation found for network_available_sign_in_detailed (7520423801613396556) -->
     <skip />
     <string name="wifi_no_internet" msgid="1386911698276448061">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> Интернетке туташуусу жок"</string>
-    <string name="wifi_no_internet_detailed" msgid="634938444133558942">"Параметрлерди ачуу үчүн таптап коюңуз"</string>
+    <string name="wifi_no_internet_detailed" msgid="634938444133558942">"Параметрлерди ачуу үчүн тийип коюңуз"</string>
     <string name="mobile_no_internet" msgid="4014455157529909781">"Мобилдик Интернет жок"</string>
     <string name="other_networks_no_internet" msgid="6698711684200067033">"Тармактын Интернет жок"</string>
     <string name="private_dns_broken_detailed" msgid="3709388271074611847">"Жеке DNS сервери жеткиликсиз"</string>
@@ -1396,7 +1396,7 @@
     <string name="usb_midi_notification_title" msgid="7404506788950595557">"USB аркылуу MIDI режими күйгүзүлдү"</string>
     <string name="usb_uvc_notification_title" msgid="2030032862673400008">"Түзмөк веб-камера катары туташты"</string>
     <string name="usb_accessory_notification_title" msgid="1385394660861956980">"USB шайманы туташты"</string>
-    <string name="usb_notification_message" msgid="4715163067192110676">"Кошумча параметрлерди ачуу үчүн таптап коюңуз."</string>
+    <string name="usb_notification_message" msgid="4715163067192110676">"Кошумча параметрлерди ачуу үчүн тийип коюңуз."</string>
     <string name="usb_power_notification_message" msgid="7284765627437897702">"Туташкан түзмөк кубатталууда. Дагы параметрлерди көрүү үчүн таптап коюңуз."</string>
     <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Аналогдук аудио жабдуу табылды"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"Тиркелген түзмөк бул телефонго шайкеш келбейт. Көбүрөөк маалымат алуу үчүн таптап коюңуз."</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Режимдин адаттагы билдирмеси"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Батареяны үнөмдөгүч күйгүзүлдү"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Кубат үнөмдөлсө, батарея көбүрөөк убакытка жетет"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Батареяны үнөмдөгүч режими күйүк"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Батареянын кубатынын мөөнөтүн узартуу үчүн Батареяны үнөмдөгүч режими күйгүзүлдү"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Батареяны үнөмдөгүч"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Батареяны үнөмдөө режими өчүк"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Телефондун кубаты жетиштүү. Функциялар мындан ары чектелбейт."</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Жазышуулар колдонмосун ачуу"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ал кантип иштейт"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Кезекте турат..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index b27be69..5fa0fc1 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"ການແຈ້ງເຕືອນຂໍ້ມູນໂໝດກິດຈະວັດປະຈຳວັນ"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"ເປີດໃຊ້ຕົວປະຢັດແບັດເຕີຣີແລ້ວ"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"ກຳລັງຫຼຸດການໃຊ້ແບັດເຕີຣີເພື່ອຍືດອາຍຸແບັດເຕີຣີ"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"ຕົວປະຢັດແບັດເຕີຣີເປີດຢູ່"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"ຕົວປະຢັດແບັດເຕີຣີເປີດຢູ່ເພື່ອຍືດອາຍຸແບັດເຕີຣີ"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ຕົວປະຢັດແບັດເຕີຣີ"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"ປິດຕົວປະຢັດແບັດເຕີຣີແລ້ວ"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"ໂທລະສັບມີໄຟພຽງພໍແລ້ວ. ບໍ່ມີການຈຳກັດຄຸນສົມບັດອີກຕໍ່ໄປແລ້ວ."</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"ເປີດ Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ມັນເຮັດວຽກແນວໃດ"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"ລໍຖ້າດຳເນີນການ..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 28d8e25..e06c2ef 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -2146,10 +2146,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Veiksmų sekos režimo informacijos pranešimas"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Įjungta akumuliatoriaus tausojimo priemonė"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Sumažinamas akumuliatoriaus energijos vartojimas, kad akumuliatorius veiktų ilgiau"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Akumuliatoriaus tausojimo priemonė įjungta"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Akumuliatoriaus tausojimo priemonė įjungta, kad akumuliatorius veiktų ilgiau"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Akumuliatoriaus tausojimo priemonė"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Akumuliatoriaus tausojimo priemonė išjungta"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefonas pakankamai įkrautas. Funkcijos neberibojamos."</string>
@@ -2417,4 +2415,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Atidaryti programą „Messages“"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kaip tai veikia"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Laukiama..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index b745ff4..d2a6cd9 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -2145,10 +2145,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Informatīvs paziņojums par akumulatoru"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Ieslēgts akumulatora enerģijas taupīšanas režīms"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Tiek samazināts akumulatora lietojums, lai paildzinātu akumulatora darbību."</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Akumulatora enerģijas taupīšanas režīms ir ieslēgts"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Akumulatora enerģijas taupīšanas režīms ir ieslēgts, lai paildzinātu akumulatora darbību."</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Akumulatora enerģijas taupīšanas režīms"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Akumulatora enerģijas taupīšanas režīms ir izslēgts"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Tālruņa uzlādes līmenis ir pietiekams. Funkcijas vairs netiek ierobežotas."</string>
@@ -2416,4 +2414,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Atvērt lietotni Ziņojumi"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Darbības principi"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Gaida…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 646f2a1..796f9efe 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -153,7 +153,7 @@
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> по <xliff:g id="TIME_DELAY">{2}</xliff:g> секунди"</string>
     <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не е препратено"</string>
     <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не е проследен"</string>
-    <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Обезбедување на мобилна мрежа"</string>
+    <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Безбедност на мобилна мрежа"</string>
     <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Шифрирање, известувања за нешифрирани мрежи"</string>
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Пристапено е до ID на уредот"</string>
     <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Во <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, мрежа во близина го сними уникатниот ID (IMSI или IMEI) на вашиот телефон со користење на вашата SIM-картичка на <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string>
@@ -161,9 +161,9 @@
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Поврзано со шифрирана мрежа <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Врската со SIM-картичката на <xliff:g id="NETWORK_NAME">%1$s</xliff:g> сега е побезбедна"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Поврзано со нешифрирана мрежа"</string>
-    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Повиците, пораките и податоците во моментов се почувствителни додека ја користите вашата SIM-картичка на <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
+    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Повиците, пораките и податоците се почувствителни кога го користите вашиот SIM од <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Повиците, пораките и податоците во моментов се почувствителни додека ја користите вашата SIM-картичка на <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nКога вашата врска ќе биде шифрирана повторно, ќе добиете уште едно известување."</string>
-    <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Поставки за обезбедување на мобилна мрежа"</string>
+    <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Поставки за безбедност на мобилната мрежа"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Дознајте повеќе"</string>
     <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Сфатив"</string>
     <string name="fcComplete" msgid="1080909484660507044">"Кодот за карактеристиката заврши."</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Известување за информации за режимот за рутини"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"„Штедачот на батерија“ е вклучен"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Се намалува користењето на батеријата за нејзино подолго траење"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"„Штедачот на батерија“ е вклучен"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"„Штедачот на батерија“ е вклучен за да се продолжи траењето на батеријата"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Штедач на батерија"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Штедачот на батерија е исклучен"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Телефонот е доволно полн. Функциите веќе не се ограничени."</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Работен профил 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Профил за тестирање"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Профил на заедницата"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Работен профил"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Приватен простор"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Клониран профил"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Профил на заедницата"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Содржината на чувствителните известувања е скриена"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Од безбедносни причини, содржините на апликацијата се скриени од споделувањето екран"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Отворете ја Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Дознајте како функционира"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Во фаза на чекање…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index a763c27..a0b4c8d 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"ദിനചര്യ മോഡ് വിവരത്തെ കുറിച്ചുള്ള അറിയിപ്പ്"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"ബാറ്ററി സേവർ ഓണാക്കി"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"ബാറ്ററി ലൈഫ് വർദ്ധിപ്പിക്കാൻ ബാറ്ററി ഉപയോഗം കുറയ്ക്കുന്നു"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"ബാറ്ററി സേവർ ഓണാണ്"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"ബാറ്ററി ലൈഫ് വർദ്ധിപ്പിക്കാൻ ബാറ്ററി സേവർ ഓണാക്കിയിരിക്കുന്നു"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ബാറ്ററി ലാഭിക്കൽ"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"ബാറ്ററി ലാഭിക്കൽ ഓഫാക്കിയിരിക്കുന്നു"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"ഫോണിൽ വേണ്ടത്ര ചാർജ് ഉണ്ട്. ഫീച്ചറുകൾക്ക് ഇനി നിയന്ത്രണമില്ല."</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages തുറക്കുക"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ഇത് പ്രവർത്തിക്കുന്നത് എങ്ങനെയാണ്"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"തീർപ്പാക്കിയിട്ടില്ല..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 6f5e4b8..5ba16fe 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -1762,7 +1762,7 @@
     <string name="user_switched" msgid="7249833311585228097">"Одоогийн хэрэглэгч <xliff:g id="NAME">%1$s</xliff:g>."</string>
     <string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> руу сэлгэж байна…"</string>
     <string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>-с гарч байна…"</string>
-    <string name="owner_name" msgid="8713560351570795743">"Эзэмшигч"</string>
+    <string name="owner_name" msgid="8713560351570795743">"Өмчлөгч"</string>
     <string name="guest_name" msgid="8502103277839834324">"Зочин"</string>
     <string name="error_message_title" msgid="4082495589294631966">"Алдаа"</string>
     <string name="error_message_change_not_allowed" msgid="843159705042381454">"Энэ өөрчлөлтийг админ зөвшөөрөөгүй байна"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Хэвшлийн горимын мэдээллийн мэдэгдэл"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Батарей хэмнэгчийг асаасан"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Батарейн ажиллах хугацааг уртасгахын тулд батарей ашиглалтыг багасгаж байна"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Батарей хэмнэгч асаалттай байна"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Батарей хэмнэгчийг батарейн ажиллах хугацааг уртасгахын тулд асаасан"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Батарей хэмнэгч"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Батарей хэмнэгчийг унтраалаа"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Утас хангалттай цэнэгтэй боллоо. Онцлогуудыг цаашид хязгаарлахгүй."</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Мессежийг нээх"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Энэ хэрхэн ажилладаг вэ?"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Хүлээгдэж буй..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 9c9e072..6c19cc5 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"दिनक्रम मोडची माहिती सूचना"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"बॅटरी सेव्हर सुरू केला आहे"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"बॅटरी लाइफ वाढवण्यासाठी बॅटरी वापर कमी करा"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"बॅटरी सेव्हर सुरू आहे"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"बॅटरी लाइफ वाढवण्यासाठी बॅटरी सेव्हर सुरू केले आहे"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"बॅटरी सेव्‍हर"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"बॅटरी सेव्हर बंद केलेला आहे"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"फोन पुरेसा चार्ज केलेला आहे. वैशिष्ट्ये मर्यादित नाहीत."</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages उघडा"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ते कसे काम करते"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"प्रलंबित आहे..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index c0ba454..82c1a59 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -157,7 +157,7 @@
     <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Penyulitan, pemberitahuan untuk rangkaian yang tidak disulitkan"</string>
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"ID peranti diakses"</string>
     <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Pada <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, rangkaian berdekatan merekodkan ID unik peranti anda (IMSI atau IMEI) semasa menggunakan SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> anda"</string>
-    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Pada <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, rangkaian berdekatan merekodkan ID unik peranti anda (IMSI atau IMEI) semasa menggunakan SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> anda.\n\nHal ini bermaksud bahawa lokasi, aktiviti atau identiti anda telah dilog. Amalan ini biasa dilakukan tetapi mungkin sukar dilakukan oleh pengguna yang bimbang tentang privasi."</string>
+    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Pada <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, rangkaian berdekatan merekodkan ID unik peranti anda (IMSI atau IMEI) semasa menggunakan SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> anda.\n\nHal ini bermaksud bahawa lokasi, aktiviti atau identiti anda telah dilog. Amalan ini biasa dilakukan tetapi mungkin menjadi hal kepada pengguna yang bimbang tentang privasi."</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Disambungkan kepada rangkaian <xliff:g id="NETWORK_NAME">%1$s</xliff:g> yang disulitkan"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Sambungan SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> kini lebih selamat"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Disambungkan kepada rangkaian yang tidak disulitkan"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Pemberitahuan maklumat Mod Rutin"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Penjimat Bateri dihidupkan"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Mengurangkan penggunaan bateri untuk melanjutkan hayat bateri"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Penjimat Bateri dihidupkan"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Penjimat Bateri dihidupkan untuk melanjutkan hayat bateri"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Penjimat Bateri"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Penjimat Bateri dimatikan"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Cas telefon mencukupi. Ciri tidak lagi dihadkan."</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Kerja 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Ujian"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Umum"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profil kerja"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Ruang privasi"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Umum"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Kandungan pemberitahuan yang sensitif disembunyikan"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Kandungan apl disembunyikan daripada perkongsian skrin untuk keselamatan"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Buka Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cara ciri ini berfungsi"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Belum selesai..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 305cfc74..43b7ffa 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -157,12 +157,12 @@
     <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"အသွင်ဝှက်ခြင်း၊ အသွင်ဝှက်မထားသော ကွန်ရက်များအတွက် အကြောင်းကြားချက်များ"</string>
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"စက် ID သုံးထားသည်"</string>
     <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> တွင် သင့် <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> ဆင်းမ်ကတ်ကို သုံးနေစဉ် အနီးရှိ ကွန်ရက်သည် စက်ပစ္စည်း၏ သီးသန့် ID (IMSI (သို့) IMEI) ကို မှတ်တမ်းတင်ထားသည်"</string>
-    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> တွင် သင့် <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> ဆင်းမ်ကတ်ကို သုံးနေစဉ် အနီးရှိ ကွန်ရက်သည် စက်ပစ္စည်း၏ သီးသန့် ID (IMSI (သို့) IMEI) ကို မှတ်တမ်းတင်ထားသည်။\n\nဆိုလိုသည်မှာ သင့်တည်နေရာ၊ လုပ်ဆောင်ချက် (သို့) အထောက်အထားကို မှတ်တမ်းတင်ထားသည်။ ၎င်းသည် သာမန်လုပ်ဆောင်မှုဖြစ်သော်လည်း ကိုယ်ရေးအချက်အလက်လုံခြုံမှုနှင့်ပတ်သက်၍ စိုးရိမ်သောသူများအတွက် ပြဿနာတစ်ခု ဖြစ်နိုင်သည်။"</string>
+    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> တွင် သင့် <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> ဆင်းမ်ကတ်ကို သုံးနေစဉ် အနီးရှိ ကွန်ရက်သည် စက်ပစ္စည်း၏ သီးသန့် ID (IMSI (သို့) IMEI) ကို မှတ်တမ်းတင်ထားသည်။\n\nဆိုလိုသည်မှာ သင့်တည်နေရာ၊ လုပ်ဆောင်ချက် (သို့) အထောက်အထားကို မှတ်တမ်းတင်ထားသည်။ ၎င်းသည် သာမန်လုပ်ဆောင်မှုဖြစ်သော်လည်း ကိုယ်ရေးအချက်အလက်လုံခြုံမှုနှင့် ပတ်သက်၍ အချို့သူများအတွက် စိုးရိမ်စရာ ဖြစ်နိုင်သည်။"</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"အသွင်ဝှက်ထားသော ကွန်ရက် <xliff:g id="NETWORK_NAME">%1$s</xliff:g> သို့ ချိတ်ဆက်ထားသည်"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> ဆင်းမ်ကတ် ချိတ်ဆက်မှုသည် ယခု ပိုမိုလုံခြုံပါပြီ"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"အသွင်ဝှက်မထားသော ကွန်ရက်သို့ ချိတ်ဆက်ထားသည်"</string>
-    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"သင့် <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ဆင်းမ်ကတ်ကို သုံးနေစဉ် ဖုန်းခေါ်ဆိုမှု၊ မက်ဆေ့ဂျ်နှင့် ဒေတာများသည် လက်ရှိတွင် ပိုမိုထိခိုက်လွယ်သည်"</string>
-    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"သင့် <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ဆင်းမ်ကတ်ကို သုံးနေစဉ် ဖုန်းခေါ်ဆိုမှု၊ မက်ဆေ့ဂျ်နှင့် ဒေတာများသည် လက်ရှိတွင် ပိုမိုထိခိုက်လွယ်သည်။\n\nသင့်ချိတ်ဆက်မှုကို ထပ်မံအသွင်ဝှက်လိုက်သောအခါ အကြောင်းကြားချက်နောက်တစ်ခု ရရှိပါမည်။"</string>
+    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"သင့် <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ဆင်းမ် သုံးချိန်တွင် ဖုန်း၊ မက်ဆေ့ဂျ်၊ ဒေတာ လုံခြုံမှုအားနည်းသည်"</string>
+    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"သင့် <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ဆင်းမ် သုံးချိန်တွင် ဖုန်း၊ မက်ဆေ့ဂျ်၊ ဒေတာ လုံခြုံမှုအားနည်းသည်။\n\nသင့်ချိတ်ဆက်မှုကို ထပ်မံအသွင်ဝှက်ပါက အကြောင်းကြားချက်နောက်တစ်ခု ရရှိပါမည်။"</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"မိုဘိုင်းကွန်ရက် လုံခြုံရေး ဆက်တင်များ"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"ပိုမိုလေ့လာရန်"</string>
     <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"နားလည်ပြီ"</string>
@@ -644,7 +644,7 @@
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"ရှေ့ဆက်ရန် သင်၏ ဇီဝမက်ထရစ်အချက်အလက် (သို့) ဖန်သားပြင်လော့ခ်ကို သုံးပါ"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ဇီဝအချက်အလက်သုံး ကွန်ပျူတာစက်ပစ္စည်း မရရှိနိုင်ပါ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"အထောက်အထားစိစစ်ခြင်းကို ပယ်ဖျက်လိုက်သည်"</string>
-    <string name="biometric_not_recognized" msgid="5106687642694635888">"မသိ"</string>
+    <string name="biometric_not_recognized" msgid="5106687642694635888">"မသိပါ"</string>
     <string name="biometric_face_not_recognized" msgid="5535599455744525200">"မျက်နှာကို မသိရှိပါ"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"အထောက်အထားစိစစ်ခြင်းကို ပယ်ဖျက်လိုက်သည်"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"ပင်နံပါတ်၊ လော့ခ်ပုံစံ သို့မဟုတ် စကားဝှက် သတ်မှတ်မထားပါ"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"ပုံမှန်မုဒ်အတွက် အချက်အလက်ပြသည့် အကြောင်းကြားချက်"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"ဘက်ထရီ အားထိန်း ဖွင့်ထားသည်"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"ဘက်ထရီသက်တမ်း ပိုရှည်စေရန် ဘက်ထရီ အသုံးပြုမှု လျှော့ချခြင်း"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"‘ဘက်ထရီ အားထိန်း’ ဖွင့်ထားသည်"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"ဘက်ထရီ သက်တမ်းရှည်စေရန် ‘ဘက်ထရီ အားထိန်း’ ဖွင့်ထားသည်"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ဘက်ထရီ အားထိန်း"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"ဘက်ထရီ အားထိန်းကို ပိတ်ထားသည်"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"ဖုန်းတွင် ဘက်ထရီအား အလုံအလောက် ရှိသည်။ လုပ်ဆောင်ချက်များကို ကန့်သတ်မထားတော့ပါ။"</string>
@@ -2222,7 +2220,7 @@
     <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"အလုပ်သုံးအက်ပ်သို့ ပြောင်းမလား။"</string>
     <string name="miniresolver_call_information" msgid="6739417525304184083">"သင့်အဖွဲ့အစည်းသည် သင့်အား အလုပ်သုံးအက်ပ်များမှသာ ဖုန်းဆက်ခွင့်ပြုသည်"</string>
     <string name="miniresolver_sms_information" msgid="4311292661329483088">"သင့်အဖွဲ့အစည်းသည် သင့်အား အလုပ်သုံးအက်ပ်များမှသာ မက်ဆေ့ဂျ်ပို့ခွင့်ပြုသည်"</string>
-    <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"သင့်ကိုယ်ပိုင် ‘ဖုန်းအက်ပ်’ မှသာ ဖုန်းခေါ်ဆိုနိုင်သည်။ ကိုယ်ပိုင် ‘ဖုန်း’ ဖြင့် ပြုလုပ်သော ခေါ်ဆိုမှုများကို သင်၏ကိုယ်ပိုင် ခေါ်ဆိုမှုမှတ်တမ်းသို့ ထည့်ပါမည်။"</string>
+    <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"သင့်ကိုယ်ပိုင် ‘ဖုန်းအက်ပ်’ မှသာ ဖုန်းခေါ်ဆိုနိုင်သည်။ ကိုယ်ပိုင် ‘ဖုန်း’ ဖြင့် ခေါ်ဆိုမှုများကို သင်၏ ကိုယ်ပိုင် ခေါ်ဆိုမှုမှတ်တမ်းသို့ ထည့်ပါမည်။"</string>
     <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"သင့်ကိုယ်ပိုင် Messages အက်ပ်မှသာ SMS မက်ဆေ့ဂျ်များကို ပို့နိုင်သည်။"</string>
     <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"ကိုယ်ပိုင်ဘရောင်ဇာ သုံးရန်"</string>
     <string name="miniresolver_use_work_browser" msgid="543575306251952994">"အလုပ်သုံးဘရောင်ဇာ သုံးရန်"</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ဖွင့်ရန်"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"အလုပ်လုပ်ပုံ"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"ဆိုင်းငံ့ထားသည်…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 1625c85..ad4ffc2 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -156,7 +156,7 @@
     <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Sikkerhet for mobilnettverk"</string>
     <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Kryptering, varsler for ukrypterte nettverk"</string>
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Enhets-ID-en er lest"</string>
-    <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> registrerte et nettverk i nærheten den unike ID-en (IMSI eller IMEI) til enheten din mens du brukte SIM-kortet fra <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string>
+    <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> registrerte et nettverk i nærheten enhetens unike ID (IMSI eller IMEI) mens du brukte <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-SIM-kortet"</string>
     <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> registrerte et nettverk i nærheten den unike ID-en (IMSI eller IMEI) til enheten din mens du brukte SIM-kortet fra <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nDette betyr at posisjonen, aktiviteten eller identiteten din er registrert. Dette er en vanlig praksis, men kan være et problem for folk som er opptatt av personvern."</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Koblet til det krypterte nettverket <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Tilkoblingen til SIM-kortet fra <xliff:g id="NETWORK_NAME">%1$s</xliff:g> er sikrere nå"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Varsel med informasjon om rutinemodus"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Batterisparing er slått på"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reduserer batteribruken for å forlenge batterilevetiden"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Batterisparing er på"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Batterisparing er slått på for å forlenge batterilevetiden"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Batterisparing"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Batterisparing er slått av"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefonen har nok batteri. Funksjoner begrenses ikke lenger."</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Jobb 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Felles"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Jobbprofil"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privat område"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Felles"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Sensitivt varselinnhold er skjult"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Av sikkerhetsgrunner er appinnholdet skjult for skjermdelingen"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Åpne Meldinger"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Slik fungerer det"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Venter …"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index d0647985..1cde247 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -84,7 +84,7 @@
     <string name="NetworkPreferenceSwitchTitle" msgid="1008329951315753038">"मोबाइल नेटवर्कमाथि पहुँच राख्न सकिएन"</string>
     <string name="NetworkPreferenceSwitchSummary" msgid="2086506181486324860">"रुचाइएको नेटवर्क परिवर्तन गरी हेर्नुहोस्‌। परिवर्तन गर्न ट्याप गर्नुहोस्‌।"</string>
     <string name="EmergencyCallWarningTitle" msgid="1615688002899152860">"आपत्‌कालीन कल सेवा अनुपलब्ध छ"</string>
-    <string name="EmergencyCallWarningSummary" msgid="9102799172089265268">"आपत्कालीन कलहरू गर्न मोबाइल नेटवर्क चाहिन्छ"</string>
+    <string name="EmergencyCallWarningSummary" msgid="9102799172089265268">"आपत्‌कालीन कलहरू गर्न मोबाइल नेटवर्क चाहिन्छ"</string>
     <string name="notification_channel_network_alert" msgid="4788053066033851841">"अलर्टहरू"</string>
     <string name="notification_channel_call_forward" msgid="8230490317314272406">"कल फर्वार्ड गर्ने सेवा"</string>
     <string name="notification_channel_emergency_callback" msgid="54074839059123159">"आपत्‌कालीन कलब्याक मोड"</string>
@@ -156,8 +156,8 @@
     <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"मोबाइल नेटवर्कको सुरक्षा"</string>
     <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"इन्क्रिप्सन, इन्क्रिप्ट नगरिएका नेटवर्कसम्बन्धी सूचना"</string>
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"डिभाइसको ID एक्सेस गरियो"</string>
-    <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"तपाईंले आफ्नो <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM प्रयोग गर्दै गर्दा तपाईंको डिभाइसको नजिकै रहेको एउटा नेटवर्कले <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> बजे तपाईंको डिभाइसको अद्वितीय ID रेकर्ड गरेको छ"</string>
-    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"तपाईंले आफ्नो <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM प्रयोग गर्दै गर्दा तपाईंको डिभाइसको नजिकै रहेको एउटा नेटवर्कले <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> बजे तपाईंको डिभाइसको अद्वितीय ID रेकर्ड गरेको छ।\n\nयसको अर्थ तपाईंको लोकेसन, गतिविधि वा पहिचान लग गरिएको छ। यसो गर्नु सामान्य हो तर आफ्नो गोपनीयताका बारेमा चिन्ता लिने मान्छेहरूका लागि यो समस्याको विषय हुन सक्छ।"</string>
+    <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"तपाईंले आफ्नो <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM प्रयोग गर्दै गर्दा तपाईंको डिभाइसको नजिकै रहेको एउटा नेटवर्कले <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> मा तपाईंको डिभाइसको अद्वितीय ID रेकर्ड गरेको छ"</string>
+    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"तपाईंले आफ्नो <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM प्रयोग गर्दै गर्दा तपाईंको डिभाइसको नजिकै रहेको एउटा नेटवर्कले <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> मा तपाईंको डिभाइसको अद्वितीय ID रेकर्ड गरेको छ।\n\nयसको अर्थ तपाईंको लोकेसन, गतिविधि वा पहिचान लग गरिएको छ। यसो गर्नु सामान्य हो तर आफ्नो गोपनीयताका बारेमा चिन्ता लिने मान्छेहरूका लागि यो समस्याको विषय हुन सक्छ।"</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"इन्क्रिप्ट गरिएको नेटवर्क <xliff:g id="NETWORK_NAME">%1$s</xliff:g> मा कनेक्ट गरियो"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM को कनेक्सन अब सुरक्षित छ"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"इन्क्रिप्ट नगरिएको नेटवर्कमा कनेक्ट गरियो"</string>
@@ -466,11 +466,11 @@
     <string name="permdesc_broadcastSticky" product="tablet" msgid="5058486069846384013">"औपचारिक प्रसारणलाई पठाउनको लागि एउटा एपलाई अनुमति दिन्छ, जुन प्रसारण समाप्त भएपछि बाँकी रहन्छ। अत्यधिक प्रयोगले धेरै मेमोरी प्रयोग गरेको कारणले ट्याब्लेटलाई ढिलो र अस्थिर बनाउन सक्छ।"</string>
     <string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"एपलाई प्रसारण समाप्त भइसकेपछि पनि रहिरहने स्टिकी प्रसारणहरू पठाउने अनुमति दिन्छ। यो सुविधाको अत्यधिक प्रयोगले धेरै मेमोरी प्रयोग हुने भएकाले तपाईंको Android टिभी यन्त्र सुस्त वा अस्थिर हुन सक्छ।"</string>
     <string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"औपचारिक प्रसारणलाई पठाउनको लागि एक एपलाई अनुमति दिन्छ, जुन प्रसारण समाप्त भएपछि बाँकी रहन्छ। अत्यधिक प्रयोगले धेरै मेमोरी प्रयोग गरेको कारणले फोनलाई ढिलो र अस्थिर बनाउन सक्छ।"</string>
-    <string name="permlab_readContacts" msgid="8776395111787429099">"तपाईँका सम्पर्कहरू पढ्नुहोस्"</string>
+    <string name="permlab_readContacts" msgid="8776395111787429099">"तपाईँका कन्ट्याक्टहरू पढ्नुहोस्"</string>
     <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"एपलाई तपाईंको ट्याब्लेटमा भण्डारण गरिएका कन्ट्याक्टहरूसँग सम्बन्धित डेटा पढ्ने अनुमति दिन्छ। एपहरूले कन्ट्याक्टहरू बनाउने तपाईंको ट्याब्लेटमा भण्डारण गरिएका खाताहरूमाथि पनि पहुँच प्राप्त गर्ने छन्। यसमा तपाईंले स्थापना गरेका एपहरूले बनाएका खाताहरू पर्न सक्छन्। यस अनुमतिले एपहरूलाई तपाईंको सम्पर्क ठेगानासम्बन्धी डेटा सेभ गर्न दिने भएकाले हानिकारक एपहरूले तपाईंलाई थाहै नदिइकन सम्पर्क ठेगानासम्बन्धी डेटा आदान प्रदान गर्न सक्छन्।"</string>
     <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"एपलाई तपाईंको Android टिभी डिभाइसमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा पढ्न अनुमति दिन्छ। एपहरूले कन्ट्याक्टहरू बनाउने तपाईंको Android टिभी डिभाइसमा भण्डारण गरिएका खाताहरूमाथि पनि पहुँच प्राप्त गर्ने छन्। यसमा तपाईंले स्थापना गरेका एपहरूले बनाएका खाताहरू पर्न सक्छन्। यस अनुमतिले एपहरूलाई तपाईंको सम्पर्क ठेगानासम्बन्धी डेटा सेभ गर्न दिने भएकाले हानिकारक एपहरूले तपाईंलाई थाहै नदिइकन सम्पर्क ठेगानासम्बन्धी डेटा आदान प्रदान गर्न सक्छन्।"</string>
     <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"एपलाई तपाईंको फोनमा भण्डारण गरिएका कन्ट्याक्टहरूसँग सम्बन्धित डेटा पढ्ने अनुमति दिन्छ। एपहरूले कन्ट्याक्टहरू बनाउने तपाईंको फोनमा भण्डारण गरिएका खाताहरूमाथि पनि पहुँच प्राप्त गर्ने छन्। यसमा तपाईंले स्थापना गरेका एपहरूले बनाएका खाताहरू पर्न सक्छन्। यस अनुमतिले एपहरूलाई तपाईंको सम्पर्क ठेगानासम्बन्धी डेटा सेभ गर्न दिने भएकाले हानिकारक एपहरूले तपाईंलाई थाहै नदिइकन सम्पर्क ठेगानासम्बन्धी डेटा आदान प्रदान गर्न सक्छन्।"</string>
-    <string name="permlab_writeContacts" msgid="8919430536404830430">"तपाईँका सम्पर्कहरू परिवर्तन गर्नुहोस्"</string>
+    <string name="permlab_writeContacts" msgid="8919430536404830430">"तपाईँका कन्ट्याक्टहरू परिवर्तन गर्नुहोस्"</string>
     <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"एपलाई तपाईंको ट्याब्लेटमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा परिमार्जन गर्न अनुमति दिन्छ। यो अनुमतिले एपलाई सम्पर्क ठेगानासम्बन्धी डेटा मेटाउन अनुमति दिन्छ।"</string>
     <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"एपलाई तपाईंको Android टिभी डिभाइसमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा परिमार्जन गर्न अनुमति दिन्छ। यो अनुमतिले एपलाई सम्पर्क ठेगानासम्बन्धी डेटा मेटाउन अनुमति दिन्छ।"</string>
     <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"एपलाई तपाईंको फोनमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा परिमार्जन गर्न अनुमति दिन्छ। यो अनुमतिले एपलाई सम्पर्क ठेगानासम्बन्धी डेटा मेटाउन अनुमति दिन्छ।"</string>
@@ -645,14 +645,14 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"बायोमेट्रिक हार्डवेयर उपलब्ध छैन"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"प्रमाणीकरण रद्द गरियो"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"पहिचान भएन"</string>
-    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"अनुहार पहिचान गर्न सकिएन"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"अनुहार मिलेन"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"प्रमाणीकरण रद्द गरियो"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"कुनै पनि PIN, ढाँचा वा पासवर्ड सेट गरिएको छैन"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"प्रमाणित गर्ने क्रममा त्रुटि भयो"</string>
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"स्क्रिन लक प्रयोग गर्नुहोस्"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"जारी राख्न आफ्नो स्क्रिन लक हाल्नुहोस्"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"सेन्सरमा बेसरी थिच्नुहोस्"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"फिंगरप्रिन्ट पहिचान गर्न सकिएन। फेरि प्रयास गर्नुहोस्।"</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"फिंगरप्रिन्ट मिलेन। फेरि प्रयास गर्नुहोस्।"</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"फिंगरप्रिन्ट सेन्सर सफा गरेर फेरि प्रयास गर्नुहोस्"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"सेन्सर सफा गरेर फेरि प्रयास गर्नुहोस्"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"सेन्सरमा बेसरी थिच्नुहोस्"</string>
@@ -664,9 +664,9 @@
     <string name="fingerprint_acquired_immobile" msgid="1621891895241888048">"हरेक पटक आफ्नो औँला थोरै यताउता सार्नुहोस्"</string>
   <string-array name="fingerprint_acquired_vendor">
   </string-array>
-    <string name="fingerprint_error_not_match" msgid="4599441812893438961">"फिंगरप्रिन्ट पहिचान गर्न सकिएन"</string>
-    <string name="fingerprint_udfps_error_not_match" msgid="8236930793223158856">"फिंगरप्रिन्ट पहिचान गर्न सकिएन"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5590293588784953188">"अनुहार पहिचान गर्न सकिएन। बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्।"</string>
+    <string name="fingerprint_error_not_match" msgid="4599441812893438961">"फिंगरप्रिन्ट मिलेन"</string>
+    <string name="fingerprint_udfps_error_not_match" msgid="8236930793223158856">"फिंगरप्रिन्ट मिलेन"</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5590293588784953188">"अनुहार मिलेन। बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्।"</string>
     <string name="fingerprint_authenticated" msgid="2024862866860283100">"फिंगरप्रिन्ट प्रमाणीकरण गरियो"</string>
     <string name="face_authenticated_no_confirmation_required" msgid="8867889115112348167">"अनुहार प्रमाणीकरण गरियो"</string>
     <string name="face_authenticated_confirmation_required" msgid="6872632732508013755">"अनुहार प्रमाणीकरण गरियो, कृपया पुष्टि गर्नुहोस् थिच्नुहोस्"</string>
@@ -716,7 +716,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"तपाईंको अनुहार देखिएन। तपाईंको फोन आफ्नो आँखाअघि राखी समात्नुहोस्।"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"अत्यधिक हल्लियो। फोन स्थिर राख्नुहोस्।"</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"कृपया आफ्नो अनुहार पुनः दर्ता गर्नुहोस्।"</string>
-    <string name="face_acquired_too_different" msgid="4505278456634706967">"अनुहार पहिचान गर्न सकिएन। फेरि प्रयास गर्नुहोस्।"</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"अनुहार मिलेन। फेरि प्रयास गर्नुहोस्।"</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"आफ्नो टाउको थोरै यताउता सार्नुहोस्"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"आफ्नो फोनमा अझ सीधा हेर्नुहोस्"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"आफ्नो फोनमा अझ सीधा हेर्नुहोस्"</string>
@@ -1507,7 +1507,7 @@
     <string name="ime_action_previous" msgid="6548799326860401611">"अघिल्लो"</string>
     <string name="ime_action_default" msgid="8265027027659800121">"चलाउनुहोस्"</string>
     <string name="dial_number_using" msgid="6060769078933953531">\n"नम्बर डायल गर्नुहोस् <xliff:g id="NUMBER">%s</xliff:g> प्रयोग गरेर"</string>
-    <string name="create_contact_using" msgid="6200708808003692594">"सम्पर्क सिर्जना गर्नुहोस्\nयो <xliff:g id="NUMBER">%s</xliff:g> प्रयोग गरेर"</string>
+    <string name="create_contact_using" msgid="6200708808003692594">"कन्ट्याक्ट हाल्नुहोस्\nयो <xliff:g id="NUMBER">%s</xliff:g> प्रयोग गरेर"</string>
     <string name="grant_credentials_permission_message_header" msgid="5365733888842570481">"निम्न एउटा वा धेरै एपहरूले तपाईँको खातामा पहुँचको लागि अनुमति अहिले र भविष्यमा अनुरोध गर्छन्।"</string>
     <string name="grant_credentials_permission_message_footer" msgid="1886710210516246461">"के तपाईं यस अनुरोधलाई अनुमति दिन चाहनुहुन्छ?"</string>
     <string name="grant_permissions_header_text" msgid="3420736827804657201">"अनुरोध पहुँच गर्नुहोस्"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"दिनचर्या मोडको जानकारीमूलक सूचना"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"ब्याट्री सेभर अन गरिएको छ"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"ब्याट्रीको आयु बढाउन ब्याट्रीको खपत कम गरिँदै छ"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"ब्याट्री सेभर अन छ"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"ब्याट्रीको आयु बढाउन ब्याट्री सेभर अन गरिएको छ"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ब्याट्री सेभर"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"ब्याट्री सेभर अफ गरियो"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"फोनमा पर्याप्त चार्ज छ। सुविधाहरूलाई अब उप्रान्त प्रतिबन्ध लगाइँदैन।"</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages खोल्नुहोस्"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"यसले काम गर्ने तरिका"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"विचाराधीन..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index ae1d537..7bae96e 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -159,9 +159,9 @@
     <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Om <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> heeft een netwerk in de buurt de unieke ID van je apparaat (IMSI of IMEI) geregistreerd toen je je simkaart van <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> gebruikte"</string>
     <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Om <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> heeft een netwerk in de buurt de unieke ID van je apparaat (IMSI of IMEI) geregistreerd toen je je simkaart van <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> gebruikte.\n\nDit betekent dat je locatie, activiteit of identiteit is geregistreerd. Dit is gebruikelijk, maar kan een probleem zijn voor mensen die zich zorgen maken over privacy."</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Verbonden met versleuteld netwerk <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
-    <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"De verbinding van de simkaart van <xliff:g id="NETWORK_NAME">%1$s</xliff:g> is nu beter beveiligd"</string>
+    <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"De verbinding van de <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-sim is nu beter beveiligd"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Verbonden met een niet-versleuteld netwerk"</string>
-    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Gesprekken, berichten en gegevens zijn op dit moment kwetsbaarder tijdens het gebruik van je simkaart van <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
+    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Gesprekken, berichten en gegevens zijn op dit moment kwetsbaarder tijdens het gebruik van je <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-sim"</string>
     <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Gesprekken, berichten en gegevens zijn op dit moment kwetsbaarder tijdens het gebruik van je simkaart van <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nAls je verbinding weer versleuteld is, krijg je opnieuw een melding."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Instellingen voor beveiliging van mobiel netwerk"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Meer informatie"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Informatiemelding voor routinemodus"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Batterijbesparing staat aan"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Het batterijgebruik wordt beperkt om de batterijduur te verlengen"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Batterijbesparing staat aan"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Batterijbesparing is aangezet om de batterijduur te verlengen"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Batterijbesparing"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Batterijbesparing staat uit"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefoon is voldoende opgeladen. Functies worden niet meer beperkt."</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Werk 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Gemeenschappelijk"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Werkprofiel"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privégedeelte"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Kloon"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Gemeenschappelijk"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Content van gevoelige meldingen verborgen"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"App-content verborgen voor scherm delen vanwege beveiligingsrisico\'s"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Berichten openen"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Hoe het werkt"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"In behandeling…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 181e7b1..c00998a 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -156,7 +156,7 @@
     <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"ମୋବାଇଲ ନେଟୱାର୍କ ସୁରକ୍ଷା"</string>
     <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"ଏନକ୍ରିପସନ, ଏନକ୍ରିପ୍ଟ କରାଯାଇନଥିବା ନେଟୱାର୍କ ପାଇଁ ବିଜ୍ଞପ୍ତି"</string>
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"ଡିଭାଇସ ID ଆକ୍ସେସ କରାଯାଇଛି"</string>
-    <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"ଆପଣଙ୍କ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> ରେକର୍ଡ କରିବା ସମୟରେ <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>ରେ ଏକ ଆଖପାଖର ନେଟୱାର୍କ ଆପଣଙ୍କ ଡିଭାଇସର ସ୍ୱତନ୍ତ୍ର ID (IMSI କିମ୍ବା IMEI) ରେକର୍ଡ କରିଛି"</string>
+    <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"ଆପଣଙ୍କ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM ବ୍ୟବହାର କରିବା ସମୟରେ <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>ରେ ଏକ ଆଖପାଖର ନେଟୱାର୍କ ଆପଣଙ୍କ ଡିଭାଇସର ସ୍ୱତନ୍ତ୍ର ID (IMSI କିମ୍ବା IMEI) ରେକର୍ଡ କରିଛି"</string>
     <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"ଆପଣଙ୍କ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM ବ୍ୟବହାର କରିବା ସମୟରେ <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>ରେ ଏକ ଆଖପାଖର ନେଟୱାର୍କ ଆପଣଙ୍କ ଡିଭାଇସର ସ୍ୱତନ୍ତ୍ର ID (IMSI କିମ୍ବା IMEI) ରେକର୍ଡ କରିଛି। \n\nଏହାର ଅର୍ଥ ହେଉଛି ଯେ ଆପଣଙ୍କ ଲୋକେସନ, କାର୍ଯ୍ୟକଳାପ କିମ୍ବା ପରିଚୟକୁ ଲଗ କରାଯାଇଛି। ଏହା ସାଧାରଣ ଅଭ୍ୟାସ ଅଟେ କିନ୍ତୁ ଗୋପନୀୟତା ବିଷୟରେ ଚିନ୍ତିତ ଲୋକମାନଙ୍କ ପାଇଁ ଏହା ଏକ ସମସ୍ୟା ହୋଇପାରେ।"</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"ଏନକ୍ରିପ୍ଟ କରାଯାଇଥିବା ନେଟୱାର୍କ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ସହ କନେକ୍ଟ କରାଯାଇଛି"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM କନେକ୍ସନ ବର୍ତ୍ତମାନ ଅଧିକ ସୁରକ୍ଷିତ ଅଟେ"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"ନିୟମିତ ମୋଡ୍‍ ସୂଚନା ବିଜ୍ଞପ୍ତି"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"ବେଟେରୀ ସେଭର ଚାଲୁ କରାଯାଇଛି"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"ବ୍ୟାଟେରୀ ଲାଇଫ ବଢ଼ାଇବା ପାଇଁ ବ୍ୟାଟେରୀ ବ୍ୟବହାର କମ୍ କରିବା"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"ବ୍ୟାଟେରୀ ସେଭର୍‌ ଚାଲୁ‌ ଅଛି"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"ବେଟେରୀ ଲାଇଫକୁ ବଢ଼ାଇବା ପାଇଁ ବେଟେରୀ ସେଭରକୁ ଚାଲୁ କରାଯାଇଛି"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ବେଟେରୀ ସେଭର"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"ବ୍ୟାଟେରୀ ସେଭର୍ ବନ୍ଦ ଅଛି"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"ଫୋନରେ ଯଥେଷ୍ଟ ଚାର୍ଜ ଅଛି। ଫିଚରଗୁଡ଼ିକ ଆଉ ପ୍ରତିବନ୍ଧିତ ନୁହେଁ।"</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"ୱାର୍କ 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"ଟେଷ୍ଟ"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"କମ୍ୟୁନାଲ"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"ୱାର୍କ ପ୍ରୋଫାଇଲ"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"ପ୍ରାଇଭେଟ ସ୍ପେସ"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"କ୍ଲୋନ"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"କମ୍ୟୁନାଲ"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"ସମ୍ୱେଦନଶୀଳ ବିଜ୍ଞପ୍ତି ବିଷୟବସ୍ତୁକୁ ଲୁଚାଯାଇଛି"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ସୁରକ୍ଷା ପାଇଁ ସ୍କ୍ରିନ ସେୟାରରୁ ଆପ ବିଷୟବସ୍ତୁକୁ ଲୁଚାଯାଇଛି"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ଖୋଲନ୍ତୁ"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ଏହା କିପରି କାମ କରେ"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"ବାକି ଅଛି…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 598af1d..77020ac 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -161,8 +161,8 @@
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"ਇਨਕ੍ਰਿਪਟਡ ਨੈੱਟਵਰਕ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> ਸਿਮ ਕਨੈਕਸ਼ਨ ਹੁਣ ਜ਼ਿਆਦਾ ਸੁਰੱਖਿਅਤ ਹੈ"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"ਇਨਕ੍ਰਿਪਟਡ ਨੈੱਟਵਰਕ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string>
-    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"ਫ਼ਿਲਹਾਲ ਤੁਹਾਡੇ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ਸਿਮ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਸਮੇਂ ਕਾਲਾਂ, ਸੁਨੇਹਿਆਂ ਅਤੇ ਡਾਟਾ ਜ਼ਿਆਦਾ ਖਤਰੇ ਵਿੱਚ ਹਨ"</string>
-    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"ਫ਼ਿਲਹਾਲ ਤੁਹਾਡੇ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ਸਿਮ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਸਮੇਂ ਕਾਲਾਂ, ਸੁਨੇਹਿਆਂ ਅਤੇ ਡਾਟਾ ਜ਼ਿਆਦਾ ਖਤਰੇ ਵਿੱਚ ਹਨ।\n\nਤੁਹਾਡਾ ਕਨੈਕਸ਼ਨ ਦੁਬਾਰਾ ਇਨਕ੍ਰਿਪਟਡ ਹੋਣ \'ਤੇ, ਤੁਹਾਨੂੰ ਹੋਰ ਸੂਚਨਾ ਪ੍ਰਾਪਤ ਹੋਵੇਗੀ।"</string>
+    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"ਫ਼ਿਲਹਾਲ ਤੁਹਾਡੇ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ਸਿਮ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਸਮੇਂ ਕਾਲਾਂ, ਸੁਨੇਹਿਆਂ ਅਤੇ ਡਾਟੇ ਤੱਕ ਪਹੁੰਚ ਕੀਤੇ ਜਾਣ ਦਾ ਖਤਰਾ ਰਹਿੰਦਾ ਹੈ"</string>
+    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"ਫ਼ਿਲਹਾਲ ਤੁਹਾਡੇ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ਸਿਮ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਸਮੇਂ ਕਾਲਾਂ, ਸੁਨੇਹਿਆਂ ਅਤੇ ਡਾਟੇ ਤੱਕ ਪਹੁੰਚ ਕੀਤੇ ਜਾਣ ਦਾ ਖਤਰਾ ਰਹਿੰਦਾ ਹੈ।\n\nਤੁਹਾਡਾ ਕਨੈਕਸ਼ਨ ਦੁਬਾਰਾ ਇਨਕ੍ਰਿਪਟਡ ਹੋਣ \'ਤੇ, ਤੁਹਾਨੂੰ ਹੋਰ ਸੂਚਨਾ ਪ੍ਰਾਪਤ ਹੋਵੇਗੀ।"</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"ਮੋਬਾਈਲ ਨੈੱਟਵਰਕ ਸੁਰੱਖਿਆ ਸੰਬੰਧੀ ਸੈਟਿੰਗਾਂ"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"ਹੋਰ ਜਾਣੋ"</string>
     <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"ਸਮਝ ਲਿਆ"</string>
@@ -316,7 +316,7 @@
     <string name="managed_profile_app_label" msgid="367401088383965725">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਦੇ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ \'ਤੇ ਜਾਓ"</string>
     <string name="permgrouplab_contacts" msgid="4254143639307316920">"ਸੰਪਰਕ"</string>
     <string name="permgroupdesc_contacts" msgid="9163927941244182567">"ਆਪਣੇ ਸੰਪਰਕਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ"</string>
-    <string name="permgrouplab_location" msgid="1858277002233964394">"ਟਿਕਾਣਾ"</string>
+    <string name="permgrouplab_location" msgid="1858277002233964394">"ਟਿਕਾਣੇ ਦੀ ਜਾਣਕਾਰੀ"</string>
     <string name="permgroupdesc_location" msgid="1995955142118450685">"ਇਸ ਡੀਵਾਈਸ ਦੇ ਨਿਰਧਾਰਤ ਟਿਕਾਣੇ ਤੱਕ ਪਹੁੰਚੋ"</string>
     <string name="permgrouplab_calendar" msgid="6426860926123033230">"ਕੈਲੰਡਰ"</string>
     <string name="permgroupdesc_calendar" msgid="6762751063361489379">"ਤੁਹਾਡੇ ਕੈਲੰਡਰ ਤੱਕ ਪਹੁੰਚ ਕਰਨ"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"ਨਿਯਮਬੱਧ ਮੋਡ ਦੀ ਜਾਣਕਾਰੀ ਵਾਲੀ ਸੂਚਨਾ"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"ਬੈਟਰੀ ਸੇਵਰ ਚਾਲੂ ਹੈ"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"ਬੈਟਰੀ ਲਾਈਫ਼ ਵਧਾਉਣ ਲਈ ਬੈਟਰੀ ਵਰਤੋਂ ਨੂੰ ਘਟਾਉਣਾ"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"ਬੈਟਰੀ ਸੇਵਰ ਚਾਲੂ ਹੈ"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"ਬੈਟਰੀ ਲਾਈਫ਼ ਵਧਾਉਣ ਲਈ ਬੈਟਰੀ ਸੇਵਰ ਚਾਲੂ ਕੀਤਾ ਗਿਆ ਹੈ"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ਬੈਟਰੀ ਸੇਵਰ"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"ਬੈਟਰੀ ਸੇਵਰ ਬੰਦ ਕੀਤਾ ਗਿਆ"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"ਫ਼ੋਨ ਲੋੜੀਂਦਾ ਚਾਰਜ ਹੈ। ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਹੁਣ ਪ੍ਰਤਿਬੰਧਿਤ ਨਹੀਂ ਹਨ।"</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"ਕੰਮ ਸੰਬੰਧੀ 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"ਜਾਂਚ"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"ਭਾਈਚਾਰਕ"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ਕਲੋਨ"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"ਭਾਈਚਾਰਕ"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"ਲੁਕੀ ਹੋਈ ਸੰਵੇਦਨਸ਼ੀਲ ਸੂਚਨਾ ਸਮੱਗਰੀ"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ਐਪ ਸਮੱਗਰੀ ਨੂੰ ਸੁਰੱਖਿਆ ਲਈ ਸਕ੍ਰੀਨ ਸਾਂਝਾਕਰਨ ਤੋਂ ਲੁਕਾਇਆ ਗਿਆ ਹੈ"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ਐਪ ਖੋਲ੍ਹੋ"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ਇਹ ਕਿਵੇਂ ਕੰਮ ਕਰਦਾ ਹੈ"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"ਵਿਚਾਰ-ਅਧੀਨ..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 9bdea97..0971ae4 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -161,9 +161,9 @@
     <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"O godzinie <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> pobliska sieć zarejestrowała unikalny identyfikator Twojego urządzenia (IMSI lub IMEI) podczas korzystania z karty SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string>
     <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"O godzinie <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> pobliska sieć zarejestrowała unikalny identyfikator Twojego urządzenia (IMSI lub IMEI) podczas korzystania z karty SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nOznacza to, że zarejestrowano Twoją lokalizację, aktywność lub tożsamość. Jest to powszechna praktyka, ale może stanowić problem dla osób dbających o prywatność."</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Połączono z szyfrowaną siecią <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
-    <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Połączenie SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> jest teraz bezpieczniejsze"</string>
+    <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Połączenie przy użyciu karty SIM w sieci <xliff:g id="NETWORK_NAME">%1$s</xliff:g> jest teraz bezpieczniejsze"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Połączono z niezaszyfrowaną siecią"</string>
-    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Połączenia, wiadomości i dane są obecnie bardziej podatne na ataki podczas korzystania z karty SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
+    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Gdy korzystasz z karty SIM w sieci <xliff:g id="NETWORK_NAME">%1$s</xliff:g>, połączenia, wiadomości i dane są bardziej narażone na ataki"</string>
     <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Połączenia, wiadomości i dane są obecnie bardziej podatne na ataki podczas korzystania z karty SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nGdy połączenie zostanie ponownie zaszyfrowane, otrzymasz kolejne powiadomienie."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Ustawienia bezpieczeństwa sieci komórkowej"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Więcej informacji"</string>
@@ -2146,10 +2146,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Powiadomienie z informacją o trybie rutynowym"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Oszczędzanie baterii jest włączone"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Ograniczam wykorzystanie baterii, aby przedłużyć jej żywotność"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Oszczędzanie baterii jest włączone"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Oszczędzanie baterii jest włączone, aby wydłużyć jej żywotność"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Oszczędzanie baterii"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Oszczędzanie baterii zostało wyłączone"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefon jest wystarczająco naładowany. Funkcje nie są już ograniczone."</string>
@@ -2405,14 +2403,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Służbowy 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Testowy"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Wspólny"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profil służbowy"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Przestrzeń prywatna"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Wspólny"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Treść poufnego powiadomienia została ukryta"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Ze względów bezpieczeństwa zawartość aplikacji jest niewidoczna podczas udostępniania ekranu"</string>
@@ -2421,4 +2415,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otwórz Wiadomości"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Jak to działa"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Oczekiwanie…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index bef0713..b04cd4c 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -164,7 +164,7 @@
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Conectado à rede não criptografada"</string>
     <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Suas ligações, mensagens e dados estão mais vulneráveis no momento ao usar seu chip da <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Ligações, mensagens e dados estão mais vulneráveis no momento ao usar seu chip da <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nQuando a conexão for criptografada novamente, você vai receber outra notificação."</string>
-    <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Configurações de segurança de rede móvel"</string>
+    <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Configurações de segurança da rede móvel"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Saiba mais"</string>
     <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Entendi"</string>
     <string name="fcComplete" msgid="1080909484660507044">"Código de recurso concluído."</string>
@@ -2145,10 +2145,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificação de informação do modo rotina"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Economia de bateria ativada"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reduzindo o uso da bateria para prolongar a duração dela"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"A Economia de bateria está ativada"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"A Economia de bateria está ativada para prolongar a duração da carga"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Economia de bateria"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"\"Economia de bateria\" desativada"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Não há carga suficiente no smartphone. Os recursos não estão mais restritos."</string>
@@ -2404,14 +2402,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Trabalho 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Teste"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Público"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Perfil de trabalho"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espaço privado"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Público"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Conteúdo de notificação sensível oculto"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Conteúdo do app oculto no compartilhamento de tela por motivos de segurança"</string>
@@ -2420,4 +2414,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir o app Mensagens"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendente…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 6afe44d..90f9eda 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1389,7 +1389,7 @@
     <string name="no_permissions" msgid="5729199278862516390">"Não são necessárias permissões"</string>
     <string name="perm_costs_money" msgid="749054595022779685">"isto poderá estar sujeito a custos"</string>
     <string name="dlg_ok" msgid="5103447663504839312">"OK"</string>
-    <string name="usb_charging_notification_title" msgid="1674124518282666955">"O dispositivo está a carregar por USB"</string>
+    <string name="usb_charging_notification_title" msgid="1674124518282666955">"O dispositivo está a ser carregado por USB"</string>
     <string name="usb_supplying_notification_title" msgid="5378546632408101811">"A carregar o dispositivo ligado por USB"</string>
     <string name="usb_mtp_notification_title" msgid="1065989144124499810">"A transferência de ficheiros por USB está ativada"</string>
     <string name="usb_ptp_notification_title" msgid="5043437571863443281">"O PTP por USB está ativado"</string>
@@ -2145,10 +2145,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificação de informações do Modo rotina"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Poupança de bateria ativada"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reduzir a utilização da bateria para prolongar a autonomia da mesma"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Poupança de bateria ativada"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"A Poupança de bateria está ativada para prolongar a autonomia da bateria"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Poupança de bateria"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"A Poupança de bateria está desativada"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"O telemóvel tem carga suficiente. As funcionalidades já não estão restritas."</string>
@@ -2404,14 +2402,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Trabalho 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Teste"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Comum"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Perfil de trabalho"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espaço privado"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Comum"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Conteúdo das notificações sensíveis ocultado"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Conteúdo da app ocultado da partilha de ecrã por motivos de segurança"</string>
@@ -2420,4 +2414,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abre a app Mensagens"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendente…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index bef0713..b04cd4c 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -164,7 +164,7 @@
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Conectado à rede não criptografada"</string>
     <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Suas ligações, mensagens e dados estão mais vulneráveis no momento ao usar seu chip da <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Ligações, mensagens e dados estão mais vulneráveis no momento ao usar seu chip da <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nQuando a conexão for criptografada novamente, você vai receber outra notificação."</string>
-    <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Configurações de segurança de rede móvel"</string>
+    <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Configurações de segurança da rede móvel"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Saiba mais"</string>
     <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Entendi"</string>
     <string name="fcComplete" msgid="1080909484660507044">"Código de recurso concluído."</string>
@@ -2145,10 +2145,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificação de informação do modo rotina"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Economia de bateria ativada"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reduzindo o uso da bateria para prolongar a duração dela"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"A Economia de bateria está ativada"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"A Economia de bateria está ativada para prolongar a duração da carga"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Economia de bateria"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"\"Economia de bateria\" desativada"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Não há carga suficiente no smartphone. Os recursos não estão mais restritos."</string>
@@ -2404,14 +2402,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Trabalho 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Teste"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Público"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Perfil de trabalho"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espaço privado"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Público"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Conteúdo de notificação sensível oculto"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Conteúdo do app oculto no compartilhamento de tela por motivos de segurança"</string>
@@ -2420,4 +2414,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir o app Mensagens"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendente…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 6e6016d..c43df4f 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -2145,10 +2145,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificare pentru informații despre modul Rutină"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Economisirea bateriei este activată"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Se reduce utilizarea bateriei pentru a-i extinde autonomia"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Economisirea bateriei este activată"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Economisirea bateriei este activată pentru a extinde autonomia"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Economisirea bateriei"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Economisirea bateriei a fost dezactivată"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefonul este încărcat suficient. Funcțiile nu mai sunt limitate."</string>
@@ -2416,4 +2414,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Deschide Mesaje"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cum funcționează"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"În așteptare..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index dbe23ed..32a2338 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -158,11 +158,11 @@
     <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Безопасность мобильной сети"</string>
     <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Шифрование, уведомления о незашифрованных сетях"</string>
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Получен доступ к идентификатору устройства"</string>
-    <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"В <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> при использовании SIM-карты (<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>) в сети поблизости был записан уникальный идентификатор вашего устройства (IMSI или IMEI)."</string>
-    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"В <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> при использовании SIM-карты (<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>) в сети поблизости был записан уникальный идентификатор вашего устройства (IMSI или IMEI).\n\nВаше местоположение, действия или личность были зарегистрированы. Хотя в этом нет ничего необычного, раскрытие данных может доставлять проблемы людям, которые беспокоятся о своей конфиденциальности."</string>
-    <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Подключение к зашифрованной сети \"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>\""</string>
-    <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Теперь подключение SIM-карты (<xliff:g id="NETWORK_NAME">%1$s</xliff:g>) более безопасное."</string>
-    <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Подключение к незашифрованной сети"</string>
+    <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"В <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, когда вы использовали SIM-карту \"<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>\", сетью поблизости от вас был записан уникальный идентификатор вашего устройства (IMSI или IMEI)."</string>
+    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"В <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, когда вы использовали SIM-карту \"<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>\", сетью поблизости от вас был записан уникальный идентификатор вашего устройства (IMSI или IMEI).\n\nЭто значит, что было зарегистрировано ваше местоположение, действия или личность. Это распространенная практика, однако она может вызывать беспокойство у тех, кто заботится о своей конфиденциальности."</string>
+    <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Подключено к зашифрованной сети <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
+    <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Теперь подключение SIM-карты (<xliff:g id="NETWORK_NAME">%1$s</xliff:g>) лучше защищено."</string>
+    <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Подключено к незашифрованной сети"</string>
     <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Сейчас звонки, сообщения и данные более уязвимы при использовании SIM-карты (<xliff:g id="NETWORK_NAME">%1$s</xliff:g>)."</string>
     <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Сейчас звонки, сообщения и данные более уязвимы при использовании SIM-карты (<xliff:g id="NETWORK_NAME">%1$s</xliff:g>).\n\nКогда соединение будет снова зашифровано, вы получите ещё одно уведомление."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Настройки безопасности мобильной сети"</string>
@@ -2146,10 +2146,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Уведомление о батарее"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Включен режим энергосбережения"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Экономия заряда продлит время работы от батареи."</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Включен режим энергосбережения"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Это нужно, чтобы продлить время работы от батареи."</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Режим энергосбережения"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Режим энергосбережения отключен"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Телефон заряжен достаточно. Функции больше не ограничены."</string>
@@ -2417,4 +2415,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Открыть Сообщения"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Узнать принцип работы"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Обработка…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 080043c..9539cef 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"දිනචරියා ප්‍රකාර තතු දැනුම්දීම"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"බැටරි සුරැකුම ක්‍රියාත්මකයි"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"බැටරි ආයු කාලය දිගු කිරීම සඳහා බැටරි භාවිතය අඩු කිරීම"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"බැටරි සුරැකුම ක්‍රියාත්මකයි"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"බැටරි ආයු කාලය දීර්ඝ කිරීමට බැටරි සුරැකුම ක්‍රියාත්මක කර ඇත"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"බැටරි සුරැකුම"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"බැටරි සුරැකුම ක්‍රියාවිරහිත කර ඇත"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"දුරකථනයට ප්‍රමාණවත් ආරෝපණයක් තිබේ. විශේෂාංග තවදුරටත් සීමා කර නැත."</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"කාර්යාලය 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"පරීක්ෂණය"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"වාර්ගික"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"කාර්යාල පැතිකඩ"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"රහසිගත අවකාශය"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ක්ලෝන කරන්න"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"වාර්ගික"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"සංවේදී දැනුම්දීම් අන්තර්ගතය සැඟවී ඇත"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ආරක්ෂාව සඳහා යෙදුම් අන්තර්ගතය තිරය බෙදා ගැනීමෙන් සඟවා ඇත"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages විවෘත කරන්න"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"එය ක්‍රියා කරන ආකාරය"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"පොරොත්තුයි..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index d88d4f2..c72a426d 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -163,8 +163,8 @@
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Pripojené k šifrovanej sieti <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Pripojenie SIM siete <xliff:g id="NETWORK_NAME">%1$s</xliff:g> je teraz zabezpečenejšie"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Pripojené k nešifrovanej sieti"</string>
-    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Hovory, správy a údaje sú momentálne zraniteľnejšie vzhľadom na nedostatok zabezpečenia pri používaní SIM siete <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
-    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Hovory, správy a údaje sú momentálne zraniteľnejšie vzhľadom na nedostatok zabezpečenia pri používaní SIM siete <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nKeď bude vaše pripojenie znova šifrované, dostanete ďalšie upozornenie."</string>
+    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Pri používaní SIM siete <xliff:g id="NETWORK_NAME">%1$s</xliff:g> sú vaše hovory, správy a údaje zraniteľnejšie"</string>
+    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Pri používaní SIM siete <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nsú vaše hovory, správy a údaje zraniteľnejšie. Keď bude vaše pripojenie znova šifrované, dostanete ďalšie upozornenie."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Nastavenia zabezpečenia mobilnej siete"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Ďalšie informácie"</string>
     <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Dobre"</string>
@@ -2146,10 +2146,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Upozornenie s informáciami o rutinnom režime"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Bol zapnutý šetrič batérie"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Zníženie spotreby batérie predlžuje jej výdrž"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Šetrič batérie je zapnutý"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Šetrič batérie ja zapnutý, aby sa predĺžila výdrž batérie"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Šetrič batérie"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Šetrič batérie bol vypnutý."</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefón je dostatočne nabitý. Funkcie už nie sú obmedzené."</string>
@@ -2405,14 +2403,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"3. pracovný"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Testovací"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Spoločný"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Pracovný profil"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Súkromný priestor"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Spoločný"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Obsah citlivého upozornenia je skrytý"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Obsah aplikácie bol na účely zabezpečenia skrytý v zdieľaní obrazovky"</string>
@@ -2421,4 +2415,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvoriť Správy"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ako to funguje"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Nespracovaná…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 73aa2e4..e1be803 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -2146,10 +2146,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Rutinsko informativno obvestilo o načinu delovanja"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Varčevanje z energijo baterije je vklopljeno"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Manjša poraba energije baterije za podaljšanje časa delovanja baterije"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Varčevanje z energijo baterije je vklopljeno"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Varčevanje z energijo baterije je vklopljeno za podaljšanje časa delovanja pri baterijskem napajanju"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Varčevanje z energijo baterije"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Varčevanje z energijo baterije je izklopljeno"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Baterija v telefonu je dovolj napolnjena. Funkcije niso več omejene."</string>
@@ -2405,14 +2403,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Delo 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Preizkus"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Skupno"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Delovni profil"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Zasebni prostor"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Skupno"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Občutljiva vsebina obvestila je bila skrita"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Pri deljenju zaslona je vsebina aplikacije skrita zaradi varnosti"</string>
@@ -2421,4 +2415,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Odpri Sporočila"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kako deluje"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"V teku …"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 142ab0d..4d79ce7 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -157,12 +157,12 @@
     <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Enkriptimi, njoftimet për rrjetet e paenkriptuara"</string>
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Pati qasje tek ID-ja e pajisjes"</string>
     <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Në <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, një rrjet në afërsi ka regjistruar ID-në unike (IMSI ose IMEI) të pajisjes sate gjatë përdorimit të kartës sate SIM të <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string>
-    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Në <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, një rrjet në afërsi ka regjistruar ID-në unike (IMSI ose IMEI) të pajisjes sate gjatë përdorimit të kartës sate SIM të <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nKjo do të thotë që vendndodhja, aktiviteti ose identiteti yt janë regjistruar. Kjo është një praktikë të zakonshme, por mund të jetë problem për personat që janë të shqetësuar në lidhje me privatësinë."</string>
+    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Në <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, një rrjet në afërsi ka regjistruar ID-në unike (IMSI ose IMEI) të pajisjes sate gjatë përdorimit të kartës sate SIM të <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nKjo do të thotë që vendndodhja, aktiviteti ose identiteti yt janë regjistruar. Kjo është një praktikë e zakonshme, por mund të jetë problem për personat që janë të shqetësuar në lidhje me privatësinë."</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Lidhur me rrjetin e enkriptuar <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Lidhja e kartës SIM të <xliff:g id="NETWORK_NAME">%1$s</xliff:g> është më e sigurt tani"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Lidhur me një rrjet të paenkriptuar"</string>
-    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Telefonatat, mesazhet dhe të dhënat janë aktualisht më të cenueshme ndërkohë që përdoret karta jote SIM të <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
-    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Telefonatat, mesazhet dhe të dhënat janë aktualisht më të cenueshme ndërkohë që përdoret karta jote SIM të <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nKur lidhja jote të jetë përsëri e enkriptuar, do të marrësh një njoftim tjetër."</string>
+    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Telefonatat, mesazhet dhe të dhënat janë aktualisht më të cenueshme teksa përdoret karta SIM e <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
+    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Telefonatat, mesazhet dhe të dhënat janë aktualisht më të cenueshme teksa përdoret karta SIM e <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nKur lidhja jote të jetë përsëri e enkriptuar, do të marrësh një njoftim tjetër."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Cilësimet e sigurisë së rrjetit celular"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Mëso më shumë"</string>
     <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"E kuptova"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Njoftimi i informacionit të \"Modalitetit rutinë\""</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"\"Kursyesi i baterisë\" u aktivizua"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Po reduktohet përdorimi i baterisë për të rritur kohëzgjatjen e baterisë"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"\"Kursyesi i baterisë\" është aktiv"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"\"Kursyesi i baterisë\" është aktivizuar për të zgjatur kohëzgjatjen e baterisë"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Kursyesi i baterisë"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"\"Kursyesi i baterisë\" është çaktivizuar"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefoni ka nivel të mjaftueshëm baterie. Funksionet nuk janë më të kufizuara."</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Hap \"Mesazhet\""</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Si funksionon"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Në pritje..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index ac826a4..95ccead 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -161,7 +161,7 @@
     <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Мрежа у близини је у <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> евидентирала јединствени ИД вашег уређаја (IMSI или IMEI) док сте користили <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM.\n\nТо значи да је евидентирала вашу локацију, активност и идентитет. То је уобичајена пракса, али може да буде проблем људима који су забринути за приватност."</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Повезани сте на шифровану мрежу <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Веза <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM-а је сада безбеднија"</string>
-    <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Повезани сте на шифровану мрежу"</string>
+    <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Повезани сте на нешифровану мрежу"</string>
     <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Позиви, поруке и подаци су тренутно рањивији док користите <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM"</string>
     <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Позиви, поруке и подаци су тренутно рањивији док користите <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM.\n\nКада веза поново буде шифрована, послаћемо вам друго обавештење."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Подешавања безбедности на мобилној мрежи"</string>
@@ -2145,10 +2145,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Обавештење о информацијама Рутинског режима"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Уштеда батерије је укључена"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Смањује се потрошња батерије да би се продужило њено трајање"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Уштеда батерије је укључена"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Уштеда батерије је укључена да би се продужило трајање батерије"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Уштеда батерије"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Уштеда батерије је искључена"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Батерија телефона је довољно напуњена. Функције више нису ограничене."</string>
@@ -2416,4 +2414,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Отвори Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Принцип рада"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"На чекању..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 1f0b65c..0cbdda1 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Avisering om rutinläge"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Batterisparläget har aktiverats"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Minskar batteriförbrukning för att förlänga batteritiden"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Batterisparläget är aktiverat"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Batterisparläget är aktiverat för att förlänga batteritiden"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Batterisparläge"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Batterisparläget har inaktiverats"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefonen har laddats tillräckligt. Funktioner begränsas inte längre."</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Arbete 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Allmän"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Jobbprofil"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privat område"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klona"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Allmän"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Känsligt aviseringsinnehåll dolt"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Av säkerhetsskäl döljs appinnehållet vid skärmdelning"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Öppna Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Så fungerar det"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Väntar …"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 1dc75dd..10f55af 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -161,8 +161,8 @@
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Imeunganishwa kwenye mtandao uliosimbwa kwa njia fiche wa <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Sasa muunganisho wa SIM wa <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ni salama zaidi"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Imeunganishwa kwenye mtandao ambao haujasimbwa kwa njia fiche"</string>
-    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Kwa sasa maudhui ya simu, ujumbe na data yako katika hatari ya kuvamiwa unapotumia SIM yako ya <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
-    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Kwa sasa maudhui ya simu, ujumbe na data yako katika hatari ya kuvamiwa unapotumia SIM yako ya <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nMuunganisho wako ukisimbwa kwa njia fiche tena, utapata arifa nyingine."</string>
+    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Kwa sasa maudhui ya simu, ujumbe na data ipo katika hatari ya kuvamiwa unapotumia SIM yako ya <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
+    <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Kwa sasa maudhui ya simu, ujumbe na data ipo katika hatari ya kuvamiwa unapotumia SIM yako ya <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nMuunganisho wako ukisimbwa kwa njia fiche tena, utapata arifa nyingine."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Mipangilio ya usalama wa mtandao wa simu"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Pata maelezo zaidi"</string>
     <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Nimeelewa"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Arifa ya maelezo ya Hali ya Kawaida"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Kiokoa Betri kimewashwa"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Inapunguza matumizi ya betri ili kuongeza muda wa matumizi ya betri"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Kiokoa Betri kimewashwa"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Kiokoa Betri kimewashwa ili kuongeza muda wa matumizi ya betri"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Kiokoa betri"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Kiokoa Betri kimezimwa"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Simu ina chaji ya kutosha. Vipengele havizuiliwi tena."</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Wa 3 wa Kazini"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Jaribio"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Unaoshirikiwa"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Wasifu wa kazini"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Sehemu ya faragha"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Nakala"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Wasifu wa pamoja"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Maudhui nyeti kwenye arifa yamefichwa"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Maudhui ya programu yamefichwa ili yasionekane kwenye skrini ya pamoja kwa sababu za kiusalama"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Fungua Programu ya Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Utaratibu wake"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Inashughulikiwa..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index c11b9c2..466e29a 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"வழக்கமான பேட்டரி சேமிப்பானுக்கான விவர அறிவிப்பு"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"பேட்டரி சேமிப்பு இயக்கப்பட்டுள்ளது"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"பேட்டரி ஆயுளை நீட்டிக்க, பேட்டரி உபயோகத்தைக் குறைக்கிறது"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"பேட்டரி சேமிப்பான் இயக்கத்தில் உள்ளது"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"பேட்டரி ஆயுளை நீட்டிக்க பேட்டரி சேமிப்பான் இயக்கப்பட்டது"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"பேட்டரி சேமிப்பு"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"பேட்டரி சேமிப்பான் ஆஃப் செய்யப்பட்டுள்ளது"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"மொபைலில் போதுமான சார்ஜ் உள்ளது. அம்சங்கள் இனி தடையின்றி இயங்கும்."</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"பணி 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"பரிசோதனை"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"பொது"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"பணிக் கணக்கு"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"ரகசிய இடம்"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"குளோன்"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"பொது"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"உணர்வுபூர்வமான அறிவிப்பு உள்ளடக்கம் மறைக்கப்பட்டது"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"பாதுகாப்பிற்காக, திரைப் பகிர்வில் இருந்து ஆப்ஸ் உள்ளடக்கம் மறைக்கப்பட்டுள்ளது"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ஆப்ஸைத் திறக்கவும்"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"இது செயல்படும் விதம்"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"நிலுவையிலுள்ளது..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 4f27ba4..db75aac 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -156,7 +156,7 @@
     <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"మొబైల్ నెట్‌వర్క్ సెక్యూరిటీ"</string>
     <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"ఎన్‌క్రిప్షన్, ఎన్‌క్రిప్ట్ చేయని నెట్‌వర్క్‌లకు సంబంధించిన నోటిఫికేషన్‌లు"</string>
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"పరికర IDని యాక్సెస్ చేశారు"</string>
-    <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"మీ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIMను ఉపయోగిస్తున్నప్పుడు, సమీపంలోని నెట్‌వర్క్ <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> సమయానికి మీ పరికర యూనిక్ ID (IMSI లేదా IMEI)ని రికార్డ్ చేసింది"</string>
+    <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"మీ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIMను వాడేటప్పుడు, <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>కి దగ్గర్లోని ఒక నెట్‌వర్క్, మీ పరికర యూనిక్ IDని (IMSI లేదా IMEI) రికార్డ్ చేసింది"</string>
     <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"మీ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIMను ఉపయోగిస్తున్నప్పుడు, సమీపంలోని నెట్‌వర్క్ <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> సమయానికి మీ పరికర యూనిక్ ID (IMSI లేదా IMEI)ని రికార్డ్ చేసింది.\n\nమీ లొకేషన్, యాక్టివిటీ, లేదా గుర్తింపు లాగ్ అయ్యాయని దీని అర్థం. దీనిని తరచుగా అమలు చేస్తుంటారు, కానీ గోప్యత గురించి ఆందోళనపడే వ్యక్తులకు ఇది సమస్య కావచ్చు,"</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"ఎన్‌క్రిప్ట్ చేసిన నెట్‌వర్క్ <xliff:g id="NETWORK_NAME">%1$s</xliff:g>‌కు కనెక్ట్ అయింది"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM కనెక్షన్ ఇప్పుడు మరింత సురక్షితంగా ఉంది"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"రొటీన్ మోడ్ సమాచార నోటిఫికేషన్"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"బ్యాటరీ సేవర్ ఆన్ చేయబడింది"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"బ్యాటరీ జీవితకాలాన్ని పొడిగించడానికి బ్యాటరీ వినియోగాన్ని తగ్గించడం"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"బ్యాటరీ సేవర్ ఆన్‌లో ఉంది"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"బ్యాటరీ లైఫ్‌ను పొడిగించడానికి బ్యాటరీ సేవర్ ఆన్ చేయబడింది"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"బ్యాటరీ సేవర్"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"బ్యాటరీ సేవర్ ఆఫ్ చేయబడింది"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"ఫోన్‌కు తగినంత ఛార్జింగ్ ఉంది. ఫీచర్‌లు ఇప్పటి నుండి పరిమితం చేయబడవు."</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"ఆఫీస్ 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"పరీక్ష"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"కమ్యూనల్"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"వర్క్ ప్రొఫైల్"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"ప్రైవేట్ స్పేస్"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"క్లోన్"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"కమ్యూనల్"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"గోప్యమైన నోటిఫికేషన్ కంటెంట్ దాచబడింది"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"సెక్యూరిటీ కోసం స్క్రీన్ షేర్ నుండి యాప్ కంటెంట్ దాచబడింది"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messagesను తెరవండి"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ఇది ఎలా పని చేస్తుంది"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"పెండింగ్‌లో ఉంది..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 3e773e0..284a5f2 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1619,7 +1619,7 @@
     <string name="data_usage_mobile_limit_snoozed_title" msgid="101888478915677895">"เกินปริมาณเน็ตมือถือที่กำหนดไว้"</string>
     <string name="data_usage_wifi_limit_snoozed_title" msgid="1622359254521960508">"เกินขีดจำกัดของข้อมูล Wi-Fi"</string>
     <string name="data_usage_limit_snoozed_body" msgid="545146591766765678">"คุณใช้อินเทอร์เน็ตเกินไป <xliff:g id="SIZE">%s</xliff:g> จากปริมาณที่กำหนดไว้"</string>
-    <string name="data_usage_restricted_title" msgid="126711424380051268">"จำกัดอินเทอร์เน็ตที่ใช้งานอยู่เบื้องหลัง"</string>
+    <string name="data_usage_restricted_title" msgid="126711424380051268">"จำกัดข้อมูลในเบื้องหลัง"</string>
     <string name="data_usage_restricted_body" msgid="5338694433686077733">"แตะเพื่อนำข้อจำกัดออก"</string>
     <string name="data_usage_rapid_title" msgid="2950192123248740375">"ปริมาณการใช้เน็ตมือถือสูง"</string>
     <string name="data_usage_rapid_body" msgid="3886676853263693432">"แอปของคุณใช้อินเทอร์เน็ตมากกว่าปกติ"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"การแจ้งเตือนข้อมูลโหมดกิจวัตร"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"เปิดโหมดประหยัดแบตเตอรี่แล้ว"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"ลดการใช้งานแบตเตอรี่เพื่อยืดอายุการใช้งานแบตเตอรี่"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"โหมดประหยัดแบตเตอรี่เปิดอยู่"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"เปิดโหมดประหยัดแบตเตอรี่แล้วเพื่อยืดระยะเวลาการใช้งานแบตเตอรี่"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"โหมดประหยัดแบตเตอรี่"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"ปิดโหมดประหยัดแบตเตอรี่แล้ว"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"โทรศัพท์มีแบตเตอรี่เพียงพอ ไม่มีการจำกัดฟีเจอร์แล้ว"</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"งาน 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"ทดสอบ"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"ส่วนกลาง"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"โปรไฟล์งาน"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"พื้นที่ส่วนตัว"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"โคลน"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"ส่วนกลาง"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"เนื้อหาการแจ้งเตือนที่ละเอียดอ่อนซ่อนอยู่"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ซ่อนเนื้อหาแอปจากการแชร์หน้าจอเพื่อความปลอดภัย"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"เปิด Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"วิธีการทำงาน"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"รอดำเนินการ..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index e04b09a..4dd4886 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -161,7 +161,7 @@
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Nakakonekta sa naka-encrypt na network na <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Mas secure na ang koneksyon ng <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Nakakonekta sa hindi naka-encrypt na network"</string>
-    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Kasalukuyang mas nanganganib ang mga tawag, mensahe, at data habang ginagamit ang iyong <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM"</string>
+    <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Kasalukuyang walang proteksyon ang tawag, mensahe, at data habang gamit ang <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM mo"</string>
     <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Kasalukuyang mas nanganganib ang mga tawag, mensahe, at data habang ginagamit ang iyong <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM.\n\nKapag na-encrypt ulit ang iyong koneksyon, makakatanggap ka ng isa pang notification."</string>
     <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Mga setting ng seguridad ng mobile network"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Matuto pa"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notification ng impormasyon ng Routine Mode"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Na-on ang Pantipid ng Baterya"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Binabawasan ang paggamit sa baterya para mapatagal ang baterya"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Naka-on ang Pantipid ng Baterya"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Naka-on ang Pantipid ng Baterya para mapahaba ang tagal ng baterya"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Pantipid ng Baterya"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Na-off ang Pantipid ng Baterya"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"May sapat na charge ang telepono. Hindi na pinaghihigpitan ang mga feature."</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Buksan ang Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Paano ito gumagana"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Nakabinbin..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 8c9c6d8..2b616de 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Rutin Modu bilgi bildirimi"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Pil Tasarrufu açıldı"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Pil ömrünü uzatmak için pil kullanımını azaltma"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Pil Tasarrufu açık"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Pil ömrünü uzatmak için Pil Tasarrufu özelliği açıldı"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Pil Tasarrufu"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Pil Tasarrufu kapatıldı"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefon yeterince şarj oldu. Özellikler artık kısıtlanmış değil."</string>
@@ -2403,14 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"İş 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Test"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Paylaşılan"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"İş profili"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Özel alan"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Paylaşılan"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Hassas bildirim içerikleri gizlendi"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Uygulama içerikleri, güvenlik nedeniyle ekran paylaşımında gizlendi"</string>
@@ -2419,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Mesajlar\'ı aç"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"İşleyiş şekli"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Bekliyor..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 88a6616..850e21b 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -2146,10 +2146,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Сповіщення про програму"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Режим енергозбереження ввімкнено"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Заряд використовується економно, щоб подовжити час роботи акумулятора"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Режим енергозбереження ввімкнено"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Увімкнено режим енергозбереження, щоб збільшити час роботи акумулятора"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Режим енергозбереження"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Режим енергозбереження вимкнено."</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Телефон має достатньо заряду акумулятора. Функції вже не обмежено."</string>
@@ -2405,14 +2403,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"Робочий профіль 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"Тестовий профіль"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"Спільний профіль"</string>
-    <!-- no translation found for accessibility_label_managed_profile (3366526886209832641) -->
-    <skip />
-    <!-- no translation found for accessibility_label_private_profile (1436459319135548969) -->
-    <skip />
-    <!-- no translation found for accessibility_label_clone_profile (7579118375042398784) -->
-    <skip />
-    <!-- no translation found for accessibility_label_communal_profile (1437173163111334791) -->
-    <skip />
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Робочий профіль"</string>
+    <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Приватний простір"</string>
+    <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Копія профілю"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Спільний профіль"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"Чутливий вміст сповіщення приховано"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"З міркувань безпеки вміст додатка приховано під час показу екрана"</string>
@@ -2421,4 +2415,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Відкрийте Повідомлення"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Як це працює"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Обробка…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 1f0bb9b..d3fde3c 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -157,7 +157,7 @@
     <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"غیر مرموز کردہ نیٹ ورکس کے لیے مرموز کاری، اطلاعات"</string>
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"‏آلہ ID تک رسائی کی گئی"</string>
     <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"‏<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> میں، قریبی نیٹ ورک نے آپ کے <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM کا استعمال کرتے ہوئے آپ کے آلے کی منفرد ID (IMSI یا IMEI) ریکارڈ کی"</string>
-    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"‏<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> میں، آپ کے <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM استعمال کرتے ہوئے قریبی نیٹ ورک نے آپ کے آلے کی منفرد ID (IMSI یا IMEI) کو ریکارڈ کیا۔\n\nاس کا مطلب ہے کہ آپ کا مقام، سرگرمی یا شناخت لاگ ان ہو چکی ہیں۔ یہ عام بات ہے لیکن رازداری کے بارے میں فکر مند لوگوں کے لیے ایک مسئلہ ہو سکتا ہے۔"</string>
+    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"‏<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> میں، آپ کے <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM استعمال کرتے ہوئے قریبی نیٹ ورک نے آپ کے آلے کی منفرد ID (IMSI یا IMEI) کو ریکارڈ کیا۔\n\nاس کا مطلب ہے کہ آپ کا مقام، سرگرمی یا شناخت کو ریکارڈ کیا جا چکا ہے۔ یہ عام بات ہے لیکن رازداری کے بارے میں فکر مند لوگوں کے لیے ایک مسئلہ ہو سکتا ہے۔"</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"مرموز کردہ نیٹ ورک <xliff:g id="NETWORK_NAME">%1$s</xliff:g> سے منسلک ہے"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"‏‫<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM کا کنکشن اب بہت محفوظ ہے"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"غیر مرموز کردہ نیٹ ورک سے منسلک ہے"</string>
@@ -644,7 +644,7 @@
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"جاری رکھنے کے لیے اپنے بایو میٹرک اور اسکرین لاک کا استعمال کریں"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"بایومیٹرک ہارڈ ویئر دستیاب نہیں ہے"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"تصدیق کا عمل منسوخ ہو گیا"</string>
-    <string name="biometric_not_recognized" msgid="5106687642694635888">"تسلیم شدہ نہیں ہے"</string>
+    <string name="biometric_not_recognized" msgid="5106687642694635888">"شناخت نہیں ہو سکی"</string>
     <string name="biometric_face_not_recognized" msgid="5535599455744525200">"چہرے کی شناخت نہیں ہو سکی"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"تصدیق کا عمل منسوخ ہو گیا"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"کوئی پن، پیٹرن، یا پاس ورڈ سیٹ نہیں ہے"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"روٹین موڈ معلومات کی اطلاع"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"بیٹری سیور کو آن کیا گیا"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"بیٹری لائف کو بڑھانے کے لیے بیٹری کے استعمال کو کم کیا جا رہا ہے"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"بیٹری سیور آن ہے"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"بیٹری لائف کو بڑھانے کے لیے بیٹری سیور کو آن کر دیا گیا ہے"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"بیٹری سیور"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"بیٹری سیور کو آف کر دیا گیا"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"فون میں کافی چارج ہے۔ خصوصیات پر اب پابندی نہیں ہے۔"</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"پیغامات ایپ کو کھولیں"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"اس کے کام کرنے کا طریقہ"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"زیر التواء..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 21afbef..c143244 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Kun tartibi rejimi haqidagi bildirishnoma"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Quvvat tejalishi yoqildi"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Batareya tejalganda batareya quvvati uzoqroq vaqtga yetadi"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Quvvat tejash rejimi yoniq"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Batareya quvvatini uzaytirish uchun Quvvat tejash yoqilgan"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Quvvat tejash"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Quvvat tejash rejimi faolsizlantirildi"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefon yetarli quvvatlandi. Funksiyalar endi cheklovlarsiz ishlaydi."</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Xabarlar ilovasini ochish"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ishlash tartibi"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Kutilmoqda..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index d7d81d1..a0775d4 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -155,15 +155,15 @@
     <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Không được chuyển tiếp"</string>
     <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Bảo mật mạng di động"</string>
     <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Mã hoá, thông báo cho mạng chưa được mã hoá"</string>
-    <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Mã thiết bị được truy cập"</string>
+    <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Mã thiết bị đã bị truy cập"</string>
     <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Vào <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, một mạng lân cận đã ghi nhận mã nhận dạng duy nhất của thiết bị của bạn (IMSI hoặc IMEI) trong lúc sử dụng SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> của bạn"</string>
-    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Vào <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, một mạng lân cận đã ghi nhận mã nhận dạng duy nhất của thiết bị của bạn (IMSI hoặc IMEI) trong lúc sử dụng SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> của bạn.\n\nTức là vị trí, hoạt động hoặc danh tính của bạn đã được ghi lại. Đây là một phương thức thông dụng nhưng có thể trở thành vấn đề đối với những ai lo ngại về quyền riêng tư."</string>
+    <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Vào <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, một mạng lân cận đã ghi lại mã nhận dạng duy nhất của thiết bị của bạn (IMSI hoặc IMEI) trong lúc sử dụng SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> của bạn.\n\nTức là vị trí, hoạt động hoặc danh tính của bạn đã được ghi lại. Đây là một phương thức thông dụng nhưng có thể trở thành vấn đề đối với những ai lo ngại về quyền riêng tư."</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Đã kết nối với mạng <xliff:g id="NETWORK_NAME">%1$s</xliff:g> đã mã hoá"</string>
     <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Kết nối SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> nay an toàn hơn"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Đã kết nối với mạng chưa được mã hoá"</string>
     <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Cuộc gọi, tin nhắn và dữ liệu hiện dễ bị tấn công trong lúc sử dụng SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> của bạn."</string>
     <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Cuộc gọi, tin nhắn và dữ liệu hiện dễ bị tấn công trong lúc sử dụng SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> của bạn.\n\nKhi kết nối của bạn được mã hoá lại, bạn sẽ nhận được một thông báo khác."</string>
-    <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Chế độ bảo mật mạng di động"</string>
+    <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Cài đặt an ninh mạng di động"</string>
     <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Tìm hiểu thêm"</string>
     <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Tôi hiểu"</string>
     <string name="fcComplete" msgid="1080909484660507044">"Mã tính năng đã hoàn tất."</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Thông báo cung cấp thông tin về chế độ sạc thông thường"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Đã bật Trình tiết kiệm pin"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Giảm mức sử dụng pin để kéo dài thời lượng pin"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Trình tiết kiệm pin đang bật"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Trình tiết kiệm pin được bật để kéo dài thời lượng pin"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Trình tiết kiệm pin"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Trình tiết kiệm pin đã tắt"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Điện thoại còn đủ pin. Các tính năng không bị hạn chế nữa."</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Mở ứng dụng Tin nhắn"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cách hoạt động"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Đang chờ xử lý..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml
index 31acd9a..5266214 100644
--- a/core/res/res/values-watch/config.xml
+++ b/core/res/res/values-watch/config.xml
@@ -93,4 +93,7 @@
 
     <!-- If this is true, allow wake from theater mode from motion. -->
     <bool name="config_allowTheaterModeWakeFromMotion">true</bool>
+
+    <!-- True if the device supports system decorations on secondary displays. -->
+    <bool name="config_supportsSystemDecorsOnSecondaryDisplays">false</bool>
 </resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 2ee3d39..00dd3db 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -154,12 +154,12 @@
     <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:无法转接"</string>
     <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:无法转接"</string>
     <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"移动网络安全"</string>
-    <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"加密,连接到未加密网络时通知"</string>
+    <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"加密、通知(连接到未加密网络时)"</string>
     <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"有网络获取了设备 ID"</string>
     <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>,当您使用<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM 卡时,一个附近的网络记录了您设备的唯一 ID(IMSI 或 IMEI)"</string>
     <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>,当您使用<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM 卡时,一个附近的网络记录了您设备的唯一 ID(IMSI 或 IMEI)。\n\n这意味着您的位置信息、活动或身份信息都被记录了。这是常见做法,但对注重隐私的人来说可能是一个问题。"</string>
     <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"连接到了名为“<xliff:g id="NETWORK_NAME">%1$s</xliff:g>”的加密网络"</string>
-    <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM 卡连接现在更安全了"</string>
+    <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM 卡的连接现在更安全了"</string>
     <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"连接到了一个未加密的网络"</string>
     <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"目前,当您使用<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM 卡时,通话、消息和数据更易受到攻击"</string>
     <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"目前,当您使用<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM 卡时,通话、消息和数据更易受到攻击。\n\n当您的连接再次加密时,您会另收到一条通知。"</string>
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"日常安排模式信息通知"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"省电模式已开启"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"降低电池用量以延长电池续航时间"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"省电模式已开启"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"已开启省电模式以延长电池续航时间"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"省电模式"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"省电模式已关闭"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"手机电量充足。各项功能不再受限。"</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"打开“信息”应用"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"运作方式"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"待归档…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 682afea..142940a 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"「日常安排模式」資料通知"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"已開啟「慳電模式」"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"減少用電可延長電池壽命"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"「慳電模式」已開啟"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"已開啟「慳電模式」,以延長電池壽命"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"慳電模式"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"已關閉慳電模式"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"手機電量充足。各項功能已不再受限。"</string>
@@ -2403,10 +2401,10 @@
     <string name="profile_label_work_3" msgid="4834572253956798917">"工作 3"</string>
     <string name="profile_label_test" msgid="9168641926186071947">"測試"</string>
     <string name="profile_label_communal" msgid="8743921499944800427">"共用"</string>
-    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"工作資料夾"</string>
+    <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"工作設定檔"</string>
     <string name="accessibility_label_private_profile" msgid="1436459319135548969">"私人空間"</string>
     <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"複製"</string>
-    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"共通"</string>
+    <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"共用"</string>
     <string name="redacted_notification_message" msgid="1520587845842228816">"已隱藏敏感通知內容"</string>
     <string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
     <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"為安全起見,應用程式內容已從分享螢幕畫面隱藏"</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"開啟「訊息」"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"運作方式"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"待處理…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 0c18193..b7ee23c 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"日常安排模式資訊通知"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"已開啟省電模式"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"降低電池用量,以便延長電池續航力"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"省電模式已開啟"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"已開啟省電模式,延長電池續航力"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"省電模式"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"省電模式已關閉"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"手機電力充足,各項功能不再受到限制。"</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"開啟「訊息」應用程式"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"運作方式"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"待處理…"</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index c99a12e..a967fb6 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -2144,10 +2144,8 @@
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Isaziso solwazi lwe-Routine Mode"</string>
     <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Isilondolozi Sebhethri sivuliwe"</string>
     <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Ukwehlisa ukusetshenziswa kwebhethri ukuze kunwetshiswe impilo yebhethri"</string>
-    <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) -->
-    <skip />
-    <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) -->
-    <skip />
+    <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Isilondolozi sebhethri sivuliwe"</string>
+    <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Isilondolozi Sebhethri sivuliwe ukuze kunwetshwe impilo yebhethri"</string>
     <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Isilondolozi sebhethri"</string>
     <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Isilondolozi sebhethri sivaliwe"</string>
     <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Ifoni inokushajwa okwanele. Izici azisakhawulelwe."</string>
@@ -2415,4 +2413,22 @@
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Vula Imilayezo"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Indlela esebenza ngayo"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Ilindile..."</string>
+    <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) -->
+    <skip />
+    <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_title (947852541060975473) -->
+    <skip />
+    <!-- no translation found for face_dangling_notification_msg (8806849376915541655) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) -->
+    <skip />
+    <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 37d39a7..5fa13ba 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1346,6 +1346,51 @@
         <attr name="materialColorTertiary" format="color"/>
         <!-- The error color for the app, intended to draw attention to error conditions. @hide -->
         <attr name="materialColorError" format="color"/>
+
+        <!-- System Custom Tokens-->
+        <!-- @hide -->
+        <attr name="customColorWidgetBackground" format="color"/>
+        <!-- @hide -->
+        <attr name="customColorClockHour" format="color"/>
+        <!-- @hide -->
+        <attr name="customColorClockMinute" format="color"/>
+        <!-- @hide -->
+        <attr name="customColorClockSecond" format="color"/>
+        <!-- @hide -->
+        <attr name="customColorThemeApp" format="color"/>
+        <!-- @hide -->
+        <attr name="customColorOnThemeApp" format="color"/>
+        <!-- @hide -->
+        <attr name="customColorThemeAppRing" format="color"/>
+        <!-- @hide -->
+        <attr name="customColorOnThemeAppRing" format="color"/>
+        <!-- @hide -->
+        <attr name="customColorBrandA" format="color"/>
+        <!-- @hide -->
+        <attr name="customColorBrandB" format="color"/>
+        <!-- @hide -->
+        <attr name="customColorBrandC" format="color"/>
+        <!-- @hide -->
+        <attr name="customColorBrandD" format="color"/>
+        <!-- @hide -->
+        <attr name="customColorUnderSurface" format="color"/>
+        <!-- @hide -->
+        <attr name="customColorShadeActive" format="color"/>
+        <!-- @hide -->
+        <attr name="customColorOnShadeActive" format="color"/>
+        <!-- @hide -->
+        <attr name="customColorOnShadeActiveVariant" format="color"/>
+        <!-- @hide -->
+        <attr name="customColorShadeInactive" format="color"/>
+        <!-- @hide -->
+        <attr name="customColorOnShadeInactive" format="color"/>
+        <!-- @hide -->
+        <attr name="customColorOnShadeInactiveVariant" format="color"/>
+        <!-- @hide -->
+        <attr name="customColorShadeDisabled" format="color"/>
+        <!-- @hide -->
+        <attr name="customColorOverviewBackground" format="color"/>
+
     </declare-styleable>
 
     <!-- **************************************************************** -->
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index e671919..5e039b5 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -581,6 +581,51 @@
     <color name="system_on_tertiary_fixed">#FFFFFF</color>
     <color name="system_on_tertiary_fixed_variant">#FFFFFF</color>
 
+    <!--Colors used in Android system, from design system. These values can be overlaid at runtime
+     by OverlayManager RROs.-->
+    <color name="system_widget_background_light">#EEF0FF</color>
+    <color name="system_clock_hour_light">#1D2435</color>
+    <color name="system_clock_minute_light">#20386A</color>
+    <color name="system_clock_second_light">#000000</color>
+    <color name="system_theme_app_light">#2F4578</color>
+    <color name="system_on_theme_app_light">#D6DFFF</color>
+    <color name="system_theme_app_ring_light">#94AAE4</color>
+    <color name="system_on_theme_app_ring_light">#FDD7FA</color>
+    <color name="system_brand_a_light">#3A5084</color>
+    <color name="system_brand_b_light">#6E7488</color>
+    <color name="system_brand_c_light">#6076AC</color>
+    <color name="system_brand_d_light">#8C6D8C</color>
+    <color name="system_under_surface_light">#000000</color>
+    <color name="system_shade_active_light">#D9E2FF</color>
+    <color name="system_on_shade_active_light">#152E60</color>
+    <color name="system_on_shade_active_variant_light">#2F4578</color>
+    <color name="system_shade_inactive_light">#2F3036</color>
+    <color name="system_on_shade_inactive_light">#E1E2EC</color>
+    <color name="system_on_shade_inactive_variant_light">#C5C6D0</color>
+    <color name="system_shade_disabled_light">#0C0E13</color>
+    <color name="system_overview_background_light">#50525A</color>
+    <color name="system_widget_background_dark">#152E60</color>
+    <color name="system_clock_hour_dark">#9AA0B6</color>
+    <color name="system_clock_minute_dark">#D8E1FF</color>
+    <color name="system_clock_second_dark">#FFFFFF</color>
+    <color name="system_theme_app_dark">#D9E2FF</color>
+    <color name="system_on_theme_app_dark">#304679</color>
+    <color name="system_theme_app_ring_dark">#94AAE4</color>
+    <color name="system_on_theme_app_ring_dark">#E0BBDD</color>
+    <color name="system_brand_a_dark">#90A6DF</color>
+    <color name="system_brand_b_dark">#A4ABC1</color>
+    <color name="system_brand_c_dark">#7A90C8</color>
+    <color name="system_brand_d_dark">#A886A6</color>
+    <color name="system_under_surface_dark">#000000</color>
+    <color name="system_shade_active_dark">#D9E2FF</color>
+    <color name="system_on_shade_active_dark">#001945</color>
+    <color name="system_on_shade_active_variant_dark">#2F4578</color>
+    <color name="system_shade_inactive_dark">#2F3036</color>
+    <color name="system_on_shade_inactive_dark">#E1E2EC</color>
+    <color name="system_on_shade_inactive_variant_dark">#C5C6D0</color>
+    <color name="system_shade_disabled_dark">#0C0E13</color>
+    <color name="system_overview_background_dark">#C5C6D0</color>
+
     <!-- Accessibility shortcut icon background color -->
     <color name="accessibility_feature_background">#5F6368</color> <!-- Google grey 700 -->
     <color name="accessibility_magnification_background">#F50D60</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 877d11e..0676f72 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4444,8 +4444,8 @@
     <!-- True if camera app should be pinned via Pinner Service -->
     <bool name="config_pinnerCameraApp">false</bool>
 
-    <!-- True if home app should be pinned via Pinner Service -->
-    <bool name="config_pinnerHomeApp">false</bool>
+    <!-- Bytes that the PinnerService will pin for Home app -->
+    <integer name="config_pinnerHomePinBytes">0</integer>
 
     <!-- True if assistant app should be pinned via Pinner Service -->
     <bool name="config_pinnerAssistantApp">false</bool>
@@ -4652,6 +4652,19 @@
      -->
     <string-array name="config_companionDeviceCerts" translatable="false"></string-array>
 
+    <!-- A list of packages that auto-enable permissions sync feature.
+         Note that config_companionPermSyncEnabledPackages and config_companionPermSyncEnabledCerts
+         are parallel arrays.
+     -->
+    <string-array name="config_companionPermSyncEnabledPackages" translatable="false"></string-array>
+
+    <!-- A list of SHA256 Certificates corresponding to config_companionPermSyncEnabledPackages.
+         Note that config_companionPermSyncEnabledPackages and config_companionPermSyncEnabledCerts
+         are parallel arrays.
+         Example: "1A:2B:3C:4D"
+     -->
+    <string-array name="config_companionPermSyncEnabledCerts" translatable="false"></string-array>
+
     <!-- The package name for the default wellbeing app.
          This package must be trusted, as it has the permissions to control other applications
          on the device.
@@ -4704,6 +4717,15 @@
     <!-- The component name for the default system on-device sandboxed inference service. -->
     <string name="config_defaultOnDeviceSandboxedInferenceService" translatable="false"></string>
 
+    <!-- The broadcast intent name for notifying when the on-device model is loading  -->
+    <string name="config_onDeviceIntelligenceModelLoadedBroadcastKey" translatable="false"></string>
+
+    <!-- The broadcast intent name for notifying when the on-device model has been unloaded  -->
+    <string name="config_onDeviceIntelligenceModelUnloadedBroadcastKey" translatable="false"></string>
+
+    <!-- The DeviceConfig namespace for the default system on-device sandboxed inference service. -->
+    <string name="config_defaultOnDeviceIntelligenceDeviceConfigNamespace" translatable="false"></string>
+
     <!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent for
          wearable sensing. -->
     <string translatable="false" name="config_defaultWearableSensingConsentComponent"></string>
@@ -6093,6 +6115,18 @@
     <!-- Whether displaying letterbox education is enabled for letterboxed fullscreen apps. -->
     <bool name="config_letterboxIsEducationEnabled">false</bool>
 
+    <!-- The width in dp to use to detect vertical thin letterboxing.
+         If W is the available width and w is the letterbox width, an app
+         is thin letterboxed if the value here is < (W - w) / 2
+         If the value is < 0 the thin letterboxing policy is disabled -->
+    <dimen name="config_letterboxThinLetterboxWidthDp">-1dp</dimen>
+
+    <!-- The height in dp to use to detect horizontal thin letterboxing
+         If H is the available height and h is the letterbox height, an app
+         is thin letterboxed if the value here is < (H - h) / 2
+         If the value is < 0 the thin letterboxing policy is disabled -->
+    <dimen name="config_letterboxThinLetterboxHeightDp">-1dp</dimen>
+
     <!-- Default min aspect ratio for unresizable apps which are eligible for size compat mode.
          Values <= 1.0 will be ignored. Activity min/max aspect ratio restrictions will still be
          espected so this override can control the maximum screen area that can be occupied by
@@ -6919,9 +6953,6 @@
         an app is not changed during subsequent reboots.  -->
     <bool name="config_stopSystemPackagesByDefault">true</bool>
 
-    <!-- Whether to show weather on the lock screen by default. -->
-    <bool name="config_lockscreenWeatherEnabledByDefault">false</bool>
-
     <!-- Whether we should persist the brightness value in nits for the default display even if
          the underlying display device changes. -->
     <bool name="config_persistBrightnessNitsForDefaultDisplay">false</bool>
@@ -7039,6 +7070,9 @@
          event gets ignored. -->
     <integer name="config_defaultMinEmergencyGestureTapDurationMillis">200</integer>
 
+    <!-- Control whether to enable CallMetadataSyncInCallService. -->
+    <bool name="config_enableContextSyncInCall">false</bool>
+
     <!-- Whether the system uses auto-suspend mode. -->
     <bool name="config_useAutoSuspend">true</bool>
 </resources>
diff --git a/core/res/res/values/config_battery_stats.xml b/core/res/res/values/config_battery_stats.xml
index 8d97362..80cf088 100644
--- a/core/res/res/values/config_battery_stats.xml
+++ b/core/res/res/values/config_battery_stats.xml
@@ -27,16 +27,18 @@
     <!-- Whether to reset Battery Stats on unplug if the battery was significantly charged -->
     <bool name="config_batteryStatsResetOnUnplugAfterSignificantCharge">true</bool>
 
-    <!-- CPU power stats collection throttle period in milliseconds.  Since power stats collection
-    is a relatively expensive operation, this throttle period may need to be adjusted for low-power
-    devices-->
-    <integer name="config_defaultPowerStatsThrottlePeriodCpu">60000</integer>
+    <!-- Power stats collection throttle periods in milliseconds.  Since power stats collection
+    is a relatively expensive operation, these throttle period may need to be adjusted for low-power
+    devices.
 
-    <!-- Mobile Radio power stats collection throttle period in milliseconds. -->
-    <integer name="config_defaultPowerStatsThrottlePeriodMobileRadio">3600000</integer>
-
-    <!-- Mobile Radio power stats collection throttle period in milliseconds. -->
-    <integer name="config_defaultPowerStatsThrottlePeriodWifi">3600000</integer>
+    The syntax of this config string is as follows:
+    <pre>
+       power-component-name1:throttle-period-millis1 power-component-name2:throttle-period-ms2 ...
+    </pre>
+    Use "*" for the power-component-name to represent the default for all power components
+    not mentioned by name.
+    -->
+    <string name="config_powerStatsThrottlePeriods">cpu:60000 *:300000</string>
 
     <!-- PowerStats aggregation period in milliseconds. This is the interval at which the power
     stats aggregation procedure is performed and the results stored in PowerStatsStore. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 1fca4f8..2da5e9a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -794,6 +794,9 @@
     <!-- The divider symbol between different parts of the notification header including spaces. not translatable [CHAR LIMIT=3] -->
     <string name="notification_header_divider_symbol_with_spaces" translatable="false">" • "</string>
 
+    <!-- Text for inline reply button for compact conversation heads ups -->
+    <string name="notification_compact_heads_up_reply">Reply</string>
+
     <!-- Text shown in place of notification contents when the notification is hidden on a secure lockscreen -->
     <string name="notification_hidden_text">New notification</string>
 
@@ -6484,4 +6487,23 @@
     <string name="satellite_notification_how_it_works">How it works</string>
     <!-- Initial/System provided label shown for an app which gets unarchived. [CHAR LIMIT=64]. -->
     <string name="unarchival_session_app_label">Pending...</string>
+
+    <!-- Fingerprint dangling notification title -->
+    <string name="fingerprint_dangling_notification_title">Set up Fingerprint Unlock again</string>
+    <!-- Fingerprint dangling notification content for only 1 fingerprint deleted -->
+    <string name="fingerprint_dangling_notification_msg_1"><xliff:g id="fingerprint">%s</xliff:g> wasn\'t working well and was deleted</string>
+    <!-- Fingerprint dangling notification content for more than 1 fingerprints deleted -->
+    <string name="fingerprint_dangling_notification_msg_2"><xliff:g id="fingerprint">%1$s</xliff:g> and <xliff:g id="fingerprint">%2$s</xliff:g> weren\'t working well and were deleted</string>
+    <!-- Fingerprint dangling notification content for only 1 fingerprint deleted and no fingerprint left-->
+    <string name="fingerprint_dangling_notification_msg_all_deleted_1"><xliff:g id="fingerprint">%s</xliff:g> wasn\'t working well and was deleted. Set it up again to unlock your phone with fingerprint.</string>
+    <!-- Fingerprint dangling notification content for more than 1 fingerprints deleted and no fingerprint left  -->
+    <string name="fingerprint_dangling_notification_msg_all_deleted_2"><xliff:g id="fingerprint">%1$s</xliff:g> and <xliff:g id="fingerprint">%2$s</xliff:g> weren\'t working well and were deleted. Set them up again to unlock your phone with your fingerprint.</string>
+    <!-- Face dangling notification title -->
+    <string name="face_dangling_notification_title">Set up Face Unlock again</string>
+    <!-- Face dangling notification content -->
+    <string name="face_dangling_notification_msg">Your face model wasn\'t working well and was deleted. Set it up again to unlock your phone with face.</string>
+    <!-- Biometric dangling notification "set up" action button -->
+    <string name="biometric_dangling_notification_action_set_up">Set up</string>
+    <!-- Biometric dangling notification "Not now" action button -->
+    <string name="biometric_dangling_notification_action_not_now">Not now</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 54dbc48..6c6764a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -675,6 +675,8 @@
   <java-symbol type="string" name="config_companionDeviceManagerPackage" />
   <java-symbol type="array" name="config_companionDevicePackages" />
   <java-symbol type="array" name="config_companionDeviceCerts" />
+  <java-symbol type="array" name="config_companionPermSyncEnabledPackages" />
+  <java-symbol type="array" name="config_companionPermSyncEnabledCerts" />
   <java-symbol type="string" name="config_default_dns_server" />
   <java-symbol type="string" name="config_ethernet_iface_regex" />
   <java-symbol type="string" name="not_checked" />
@@ -2351,6 +2353,7 @@
   <java-symbol type="layout" name="notification_template_material_base" />
   <java-symbol type="layout" name="notification_template_material_heads_up_base" />
   <java-symbol type="layout" name="notification_template_material_compact_heads_up_base" />
+  <java-symbol type="layout" name="notification_template_material_messaging_compact_heads_up" />
   <java-symbol type="layout" name="notification_template_material_big_base" />
   <java-symbol type="layout" name="notification_template_material_big_picture" />
   <java-symbol type="layout" name="notification_template_material_inbox" />
@@ -3437,7 +3440,7 @@
   <!-- Pinner Service -->
   <java-symbol type="array" name="config_defaultPinnerServiceFiles" />
   <java-symbol type="bool" name="config_pinnerCameraApp" />
-  <java-symbol type="bool" name="config_pinnerHomeApp" />
+  <java-symbol type="integer" name="config_pinnerHomePinBytes" />
   <java-symbol type="bool" name="config_pinnerAssistantApp" />
   <java-symbol type="integer" name="config_pinnerWebviewPinBytes" />
 
@@ -3626,6 +3629,7 @@
   <java-symbol type="drawable" name="lockscreen_selected" />
 
   <java-symbol type="string" name="notification_header_divider_symbol_with_spaces" />
+  <java-symbol type="string" name="notification_compact_heads_up_reply" />
 
   <java-symbol type="color" name="notification_primary_text_color_light" />
   <java-symbol type="color" name="notification_primary_text_color_dark" />
@@ -3941,6 +3945,9 @@
   <java-symbol type="string" name="config_defaultWearableSensingService" />
   <java-symbol type="string" name="config_defaultOnDeviceIntelligenceService" />
   <java-symbol type="string" name="config_defaultOnDeviceSandboxedInferenceService" />
+  <java-symbol type="string" name="config_onDeviceIntelligenceModelLoadedBroadcastKey" />
+  <java-symbol type="string" name="config_onDeviceIntelligenceModelUnloadedBroadcastKey" />
+  <java-symbol type="string" name="config_defaultOnDeviceIntelligenceDeviceConfigNamespace" />
   <java-symbol type="string" name="config_retailDemoPackage" />
   <java-symbol type="string" name="config_retailDemoPackageSignature" />
 
@@ -4518,6 +4525,7 @@
   <java-symbol type="id" name="expand_button_container" />
   <java-symbol type="id" name="expand_button_a11y_container" />
   <java-symbol type="id" name="expand_button_touch_container" />
+  <java-symbol type="id" name="reply_action_container" />
   <java-symbol type="id" name="messaging_group_content_container" />
   <java-symbol type="id" name="expand_button_and_content_container" />
   <java-symbol type="id" name="conversation_header" />
@@ -4717,6 +4725,8 @@
   <java-symbol type="integer" name="config_letterboxDefaultPositionForTabletopModeReachability" />
   <java-symbol type="bool" name="config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled" />
   <java-symbol type="bool" name="config_letterboxIsEducationEnabled" />
+  <java-symbol type="dimen" name="config_letterboxThinLetterboxWidthDp" />
+  <java-symbol type="dimen" name="config_letterboxThinLetterboxHeightDp" />
   <java-symbol type="dimen" name="config_letterboxDefaultMinAspectRatioForUnresizableApps" />
   <java-symbol type="bool" name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled" />
   <java-symbol type="bool" name="config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled" />
@@ -5215,9 +5225,6 @@
   <java-symbol type="bool" name="config_hotspotNetworksEnabledForService"/>
   <java-symbol type="bool" name="config_knownNetworksEnabledForService"/>
 
-  <!-- Whether to show weather on the lockscreen by default. -->
-  <java-symbol type="bool" name="config_lockscreenWeatherEnabledByDefault" />
-
   <!-- For keyboard notification -->
   <java-symbol type="string" name="keyboard_layout_notification_selected_title"/>
   <java-symbol type="string" name="keyboard_layout_notification_one_selected_message"/>
@@ -5234,9 +5241,7 @@
 
   <java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" />
   <java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" />
-  <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodCpu" />
-  <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodMobileRadio" />
-  <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodWifi" />
+  <java-symbol type="string" name="config_powerStatsThrottlePeriods" />
   <java-symbol type="integer" name="config_powerStatsAggregationPeriod" />
   <java-symbol type="integer" name="config_aggregatedPowerStatsSpanDuration" />
 
@@ -5286,6 +5291,71 @@
   <java-symbol name="materialColorTertiary" type="attr"/>
   <java-symbol name="materialColorError" type="attr"/>
 
+  <java-symbol name="customColorWidgetBackground" type="attr"/>
+  <java-symbol name="customColorClockHour" type="attr"/>
+  <java-symbol name="customColorClockMinute" type="attr"/>
+  <java-symbol name="customColorClockSecond" type="attr"/>
+  <java-symbol name="customColorThemeApp" type="attr"/>
+  <java-symbol name="customColorOnThemeApp" type="attr"/>
+  <java-symbol name="customColorThemeAppRing" type="attr"/>
+  <java-symbol name="customColorOnThemeAppRing" type="attr"/>
+  <java-symbol name="customColorBrandA" type="attr"/>
+  <java-symbol name="customColorBrandB" type="attr"/>
+  <java-symbol name="customColorBrandC" type="attr"/>
+  <java-symbol name="customColorBrandD" type="attr"/>
+  <java-symbol name="customColorUnderSurface" type="attr"/>
+  <java-symbol name="customColorShadeActive" type="attr"/>
+  <java-symbol name="customColorOnShadeActive" type="attr"/>
+  <java-symbol name="customColorOnShadeActiveVariant" type="attr"/>
+  <java-symbol name="customColorShadeInactive" type="attr"/>
+  <java-symbol name="customColorOnShadeInactive" type="attr"/>
+  <java-symbol name="customColorOnShadeInactiveVariant" type="attr"/>
+  <java-symbol name="customColorShadeDisabled" type="attr"/>
+  <java-symbol name="customColorOverviewBackground" type="attr"/>
+
+  <java-symbol name="system_widget_background_light" type="color"/>
+  <java-symbol name="system_clock_hour_light" type="color"/>
+  <java-symbol name="system_clock_minute_light" type="color"/>
+  <java-symbol name="system_clock_second_light" type="color"/>
+  <java-symbol name="system_theme_app_light" type="color"/>
+  <java-symbol name="system_on_theme_app_light" type="color"/>
+  <java-symbol name="system_theme_app_ring_light" type="color"/>
+  <java-symbol name="system_on_theme_app_ring_light" type="color"/>
+  <java-symbol name="system_brand_a_light" type="color"/>
+  <java-symbol name="system_brand_b_light" type="color"/>
+  <java-symbol name="system_brand_c_light" type="color"/>
+  <java-symbol name="system_brand_d_light" type="color"/>
+  <java-symbol name="system_under_surface_light" type="color"/>
+  <java-symbol name="system_shade_active_light" type="color"/>
+  <java-symbol name="system_on_shade_active_light" type="color"/>
+  <java-symbol name="system_on_shade_active_variant_light" type="color"/>
+  <java-symbol name="system_shade_inactive_light" type="color"/>
+  <java-symbol name="system_on_shade_inactive_light" type="color"/>
+  <java-symbol name="system_on_shade_inactive_variant_light" type="color"/>
+  <java-symbol name="system_shade_disabled_light" type="color"/>
+  <java-symbol name="system_overview_background_light" type="color"/>
+  <java-symbol name="system_widget_background_dark" type="color"/>
+  <java-symbol name="system_clock_hour_dark" type="color"/>
+  <java-symbol name="system_clock_minute_dark" type="color"/>
+  <java-symbol name="system_clock_second_dark" type="color"/>
+  <java-symbol name="system_theme_app_dark" type="color"/>
+  <java-symbol name="system_on_theme_app_dark" type="color"/>
+  <java-symbol name="system_theme_app_ring_dark" type="color"/>
+  <java-symbol name="system_on_theme_app_ring_dark" type="color"/>
+  <java-symbol name="system_brand_a_dark" type="color"/>
+  <java-symbol name="system_brand_b_dark" type="color"/>
+  <java-symbol name="system_brand_c_dark" type="color"/>
+  <java-symbol name="system_brand_d_dark" type="color"/>
+  <java-symbol name="system_under_surface_dark" type="color"/>
+  <java-symbol name="system_shade_active_dark" type="color"/>
+  <java-symbol name="system_on_shade_active_dark" type="color"/>
+  <java-symbol name="system_on_shade_active_variant_dark" type="color"/>
+  <java-symbol name="system_shade_inactive_dark" type="color"/>
+  <java-symbol name="system_on_shade_inactive_dark" type="color"/>
+  <java-symbol name="system_on_shade_inactive_variant_dark" type="color"/>
+  <java-symbol name="system_shade_disabled_dark" type="color"/>
+  <java-symbol name="system_overview_background_dark" type="color"/>
+
   <java-symbol type="attr" name="actionModeUndoDrawable" />
   <java-symbol type="attr" name="actionModeRedoDrawable" />
 
@@ -5430,4 +5500,15 @@
 
   <!-- For PowerManagerService to determine whether to use auto-suspend mode -->
   <java-symbol type="bool" name="config_useAutoSuspend" />
+
+  <!-- Biometric dangling notification strings -->
+  <java-symbol type="string" name="fingerprint_dangling_notification_title" />
+  <java-symbol type="string" name="fingerprint_dangling_notification_msg_1" />
+  <java-symbol type="string" name="fingerprint_dangling_notification_msg_2" />
+  <java-symbol type="string" name="fingerprint_dangling_notification_msg_all_deleted_1" />
+  <java-symbol type="string" name="fingerprint_dangling_notification_msg_all_deleted_2" />
+  <java-symbol type="string" name="face_dangling_notification_title" />
+  <java-symbol type="string" name="face_dangling_notification_msg" />
+  <java-symbol type="string" name="biometric_dangling_notification_action_set_up" />
+  <java-symbol type="string" name="biometric_dangling_notification_action_not_now" />
 </resources>
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index ee19144..24d4938 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -284,6 +284,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
 
     <style name="Theme.DeviceDefault" parent="Theme.DeviceDefaultBase" />
@@ -380,6 +402,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault} with no action bar and no status bar.  This theme
@@ -475,6 +519,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault} with no action bar and no status bar and
@@ -572,6 +638,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault} that has no title bar and translucent
@@ -668,6 +756,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
 
     <!-- DeviceDefault theme for dialog windows and activities. This changes the window to be
@@ -772,6 +882,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault_Dialog} that has a nice minimum width for a
@@ -867,6 +999,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault_Dialog} without an action bar -->
@@ -961,6 +1115,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault_Dialog_NoActionBar} that has a nice minimum width
@@ -1056,6 +1232,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
 
     <!-- Variant of Theme.DeviceDefault.Dialog that has a fixed size. -->
@@ -1167,6 +1365,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
 
     <!-- DeviceDefault theme for a window without an action bar that will be displayed either
@@ -1263,6 +1483,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
 
     <!-- DeviceDefault theme for a presentation window on a secondary display. -->
@@ -1357,6 +1599,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
 
     <!-- DeviceDefault theme for panel windows. This removes all extraneous window
@@ -1453,6 +1717,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
 
     <!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear
@@ -1548,6 +1834,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
 
     <!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear
@@ -1643,6 +1951,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
 
     <!-- DeviceDefault style for input methods, which is used by the
@@ -1738,6 +2068,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <!-- DeviceDefault style for input methods, which is used by the
@@ -1833,6 +2185,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Material.Dialog.Alert">
@@ -1928,6 +2302,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
 
     <!-- Theme for the dialog shown when an app crashes or ANRs. -->
@@ -2028,6 +2424,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
 
     <style name="Theme.DeviceDefault.Dialog.NoFrame" parent="Theme.Material.Dialog.NoFrame">
@@ -2121,6 +2539,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault} with a light-colored style -->
@@ -2352,6 +2792,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <!-- Variant of the DeviceDefault (light) theme that has a solid (opaque) action bar with an
@@ -2447,6 +2909,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar -->
@@ -2541,6 +3025,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar and no status bar.
@@ -2636,6 +3142,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar and no status bar
@@ -2733,6 +3261,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault_Light} that has no title bar and translucent
@@ -2829,6 +3379,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <!-- DeviceDefault light theme for dialog windows and activities. This changes the window to be
@@ -2931,6 +3503,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog} that has a nice minimum width for a
@@ -3029,6 +3623,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog} without an action bar -->
@@ -3126,6 +3742,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog_NoActionBar} that has a nice minimum
@@ -3224,6 +3862,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <!-- Variant of Theme.DeviceDefault.Dialog that has a fixed size. -->
@@ -3303,6 +3963,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <!-- Variant of Theme.DeviceDefault.Dialog.NoActionBar that has a fixed size. -->
@@ -3382,6 +4064,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <!-- DeviceDefault light theme for a window that will be displayed either full-screen on smaller
@@ -3480,6 +4184,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <!-- DeviceDefault light theme for a window without an action bar that will be displayed either
@@ -3579,6 +4305,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <!-- DeviceDefault light theme for a presentation window on a secondary display. -->
@@ -3676,6 +4424,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <!-- DeviceDefault light theme for panel windows. This removes all extraneous window
@@ -3772,6 +4542,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <style name="Theme.DeviceDefault.Light.Dialog.Alert" parent="Theme.Material.Light.Dialog.Alert">
@@ -3867,6 +4659,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <style name="Theme.DeviceDefault.Dialog.Alert.DayNight" parent="Theme.DeviceDefault.Light.Dialog.Alert" />
@@ -3962,6 +4776,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <style name="Theme.DeviceDefault.Light.Voice" parent="Theme.Material.Light.Voice">
@@ -4055,6 +4891,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <!-- DeviceDefault theme for a window that should look like the Settings app.  -->
@@ -4156,6 +5014,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <style name="Theme.DeviceDefault.SystemUI" parent="Theme.DeviceDefault.Light">
@@ -4238,6 +5118,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <style name="Theme.DeviceDefault.SystemUI.Dialog" parent="Theme.DeviceDefault.Light.Dialog">
@@ -4312,6 +5214,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <!-- Variant of {@link #Theme_DeviceDefault_Settings_Dark} with no action bar -->
@@ -4407,6 +5331,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
 
     <style name="Theme.DeviceDefault.Settings.DialogBase" parent="Theme.Material.Light.BaseDialog">
@@ -4486,6 +5432,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <style name="Theme.DeviceDefault.Settings.Dialog" parent="Theme.DeviceDefault.Settings.DialogBase">
@@ -4605,6 +5573,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <style name="Theme.DeviceDefault.Settings.Dialog.Alert" parent="Theme.Material.Settings.Dialog.Alert">
@@ -4702,6 +5692,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <style name="Theme.DeviceDefault.Settings.Dialog.NoActionBar" parent="Theme.DeviceDefault.Light.Dialog.NoActionBar" />
@@ -4825,6 +5837,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
 
     <style name="ThemeOverlay.DeviceDefault.Accent.Light">
@@ -4878,6 +5912,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <!-- Theme overlay that replaces colorAccent with the colorAccent from {@link #Theme_DeviceDefault_DayNight}. -->
@@ -4935,6 +5991,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
 
     <style name="Theme.DeviceDefault.Light.Dialog.Alert.UserSwitchingDialog" parent="Theme.DeviceDefault.NoActionBar.Fullscreen">
@@ -4988,6 +6066,28 @@
         <item name="materialColorSecondary">@color/system_secondary_light</item>
         <item name="materialColorTertiary">@color/system_tertiary_light</item>
         <item name="materialColorError">@color/system_error_light</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_light</item>
+        <item name="customColorClockHour">@color/system_clock_hour_light</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_light</item>
+        <item name="customColorClockSecond">@color/system_clock_second_light</item>
+        <item name="customColorThemeApp">@color/system_theme_app_light</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item>
+        <item name="customColorBrandA">@color/system_brand_a_light</item>
+        <item name="customColorBrandB">@color/system_brand_b_light</item>
+        <item name="customColorBrandC">@color/system_brand_c_light</item>
+        <item name="customColorBrandD">@color/system_brand_d_light</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_light</item>
+        <item name="customColorShadeActive">@color/system_shade_active_light</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_light</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_light</item>
     </style>
 
     <style name="Theme.DeviceDefault.Notification" parent="@style/Theme.Material.Notification">
@@ -5052,6 +6152,28 @@
         <item name="materialColorSecondary">@color/system_secondary_dark</item>
         <item name="materialColorTertiary">@color/system_tertiary_dark</item>
         <item name="materialColorError">@color/system_error_dark</item>
+
+        <item name="customColorWidgetBackground">@color/system_widget_background_dark</item>
+        <item name="customColorClockHour">@color/system_clock_hour_dark</item>
+        <item name="customColorClockMinute">@color/system_clock_minute_dark</item>
+        <item name="customColorClockSecond">@color/system_clock_second_dark</item>
+        <item name="customColorThemeApp">@color/system_theme_app_dark</item>
+        <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item>
+        <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item>
+        <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item>
+        <item name="customColorBrandA">@color/system_brand_a_dark</item>
+        <item name="customColorBrandB">@color/system_brand_b_dark</item>
+        <item name="customColorBrandC">@color/system_brand_c_dark</item>
+        <item name="customColorBrandD">@color/system_brand_d_dark</item>
+        <item name="customColorUnderSurface">@color/system_under_surface_dark</item>
+        <item name="customColorShadeActive">@color/system_shade_active_dark</item>
+        <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item>
+        <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item>
+        <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item>
+        <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item>
+        <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item>
+        <item name="customColorOverviewBackground">@color/system_overview_background_dark</item>
     </style>
     <style name="Theme.DeviceDefault.AutofillHalfScreenDialogList" parent="Theme.DeviceDefault.DayNight">
         <item name="colorListDivider">@color/list_divider_opacity_device_default_light</item>
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 436ba15..eb3c84a 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -260,6 +260,7 @@
         "src/com/android/internal/os/**/*.java",
         "src/com/android/internal/util/**/*.java",
         "src/com/android/internal/power/EnergyConsumerStatsTest.java",
+        "src/com/android/internal/ravenwood/**/*.java",
 
         // Pull in R.java from FrameworksCoreTests-resonly, not from FrameworksCoreTests,
         // to avoid having a dependency to FrameworksCoreTests.
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 7fb894a..30ec940 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -84,7 +84,6 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.SystemProperties;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.text.Spannable;
@@ -132,9 +131,6 @@
     @Before
     public void setUp() {
         mContext = InstrumentationRegistry.getContext();
-        // TODO(b/169435530): remove this flag set once resolved.
-        SystemProperties.set("persist.sysui.notification.builder_extras_override",
-                Boolean.toString(false));
     }
 
     @Test
@@ -1703,10 +1699,6 @@
     // Ensures that extras in a Notification Builder can be updated.
     @Test
     public void testExtras_cachedExtrasOverwrittenByUserProvided() {
-        // Sets the flag to new state.
-        // TODO(b/169435530): remove this set value once resolved.
-        SystemProperties.set("persist.sysui.notification.builder_extras_override",
-                Boolean.toString(true));
         Bundle extras = new Bundle();
         extras.putCharSequence(EXTRA_TITLE, "test title");
         extras.putCharSequence(EXTRA_SUMMARY_TEXT, "summary text");
@@ -1732,10 +1724,6 @@
     // Ensures that extras in a Notification Builder can be updated by an extender.
     @Test
     public void testExtras_cachedExtrasOverwrittenByExtender() {
-        // Sets the flag to new state.
-        // TODO(b/169435530): remove this set value once resolved.
-        SystemProperties.set("persist.sysui.notification.builder_extras_override",
-                Boolean.toString(true));
         Notification.CarExtender extender = new Notification.CarExtender().setColor(1234);
 
         Notification notification = new Notification.Builder(mContext, "test id")
@@ -1749,58 +1737,6 @@
         assertThat(recoveredExtender.getColor()).isEqualTo(5678);
     }
 
-    // Validates pre-flag flip behavior, that extras in a Notification Builder cannot be updated.
-    // TODO(b/169435530): remove this test once resolved.
-    @Test
-    public void testExtras_cachedExtrasOverwrittenByUserProvidedOld() {
-        // Sets the flag to old state.
-        SystemProperties.set("persist.sysui.notification.builder_extras_override",
-                Boolean.toString(false));
-
-        Bundle extras = new Bundle();
-        extras.putCharSequence(EXTRA_TITLE, "test title");
-        extras.putCharSequence(EXTRA_SUMMARY_TEXT, "summary text");
-
-        Notification.Builder builder = new Notification.Builder(mContext, "test id")
-                .addExtras(extras);
-
-        Notification notification = builder.build();
-        assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo(
-                "test title");
-        assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo(
-                "summary text");
-
-        extras.putCharSequence(EXTRA_TITLE, "new title");
-        builder.addExtras(extras);
-        notification = builder.build();
-        assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo(
-                "test title");
-        assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo(
-                "summary text");
-    }
-
-    // Validates pre-flag flip behavior, that extras in a Notification Builder cannot be updated
-    // by an extender.
-    // TODO(b/169435530): remove this test once resolved.
-    @Test
-    public void testExtras_cachedExtrasOverwrittenByExtenderOld() {
-        // Sets the flag to old state.
-        SystemProperties.set("persist.sysui.notification.builder_extras_override",
-                Boolean.toString(false));
-
-        Notification.CarExtender extender = new Notification.CarExtender().setColor(1234);
-
-        Notification notification = new Notification.Builder(mContext, "test id")
-                .extend(extender).build();
-
-        extender.setColor(5678);
-
-        Notification.Builder.recoverBuilder(mContext, notification).extend(extender).build();
-
-        Notification.CarExtender recoveredExtender = new Notification.CarExtender(notification);
-        assertThat(recoveredExtender.getColor()).isEqualTo(1234);
-    }
-
     @Test
     @CoreCompatChangeRule.EnableCompatChanges({Notification.WEARABLE_EXTENDER_BACKGROUND_BLOCKED})
     public void wearableBackgroundBlockEnabled_wearableBackgroundSet_valueRemainsNull() {
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index b6f4429..ee1d1e1 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -65,6 +66,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.concurrent.RejectedExecutionException;
 import java.util.function.BiConsumer;
 
 /**
@@ -221,4 +223,17 @@
                 123 /* newDisplayId */, true /* shouldReportConfigChange*/);
         inOrder.verify(mController).onContextConfigurationPostChanged(context);
     }
+
+    @Test
+    public void testDisplayListenerHandlerClosed() {
+        doReturn(123).when(mActivity).getDisplayId();
+        doThrow(new RejectedExecutionException()).when(mController).onDisplayChanged(123);
+
+        mController.onContextConfigurationPreChanged(mActivity);
+        mConfiguration.windowConfiguration.setMaxBounds(new Rect(0, 0, 100, 200));
+        mController.onContextConfigurationPostChanged(mActivity);
+
+        // No crash
+        verify(mController).onDisplayChanged(123);
+    }
 }
diff --git a/core/tests/coretests/src/android/graphics/drawable/IconTest.java b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
index 950925f..e0c3b04 100644
--- a/core/tests/coretests/src/android/graphics/drawable/IconTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
@@ -475,8 +475,8 @@
         final Icon ic = Icon.createWithBitmap(bm);
         final Drawable drawable = ic.loadDrawable(mContext);
 
-        assertThat(drawable.getIntrinsicWidth()).isEqualTo(maxWidth);
-        assertThat(drawable.getIntrinsicHeight()).isEqualTo(maxHeight);
+        assertThat(Math.abs(drawable.getIntrinsicWidth() - maxWidth)).isLessThan(2);
+        assertThat(Math.abs(drawable.getIntrinsicHeight() - maxHeight)).isLessThan(2);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/net/OWNERS b/core/tests/coretests/src/android/net/OWNERS
index a779c00..beb77dc 100644
--- a/core/tests/coretests/src/android/net/OWNERS
+++ b/core/tests/coretests/src/android/net/OWNERS
@@ -1,4 +1,5 @@
 include /services/core/java/com/android/server/net/OWNERS
 
-per-file SSL*,Uri*,Url* = prb@google.com,oth@google.com,narayan@google.com,ngeoffray@google.com
+per-file SSL*,Url* = prb@google.com,oth@google.com,narayan@google.com,ngeoffray@google.com
 per-file SntpClient* = file:/services/core/java/com/android/server/timedetector/OWNERS
+per-file Uri* = varunshah@google.com
diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java
index 2a4ca79..57cb158 100644
--- a/core/tests/coretests/src/android/net/UriTest.java
+++ b/core/tests/coretests/src/android/net/UriTest.java
@@ -18,6 +18,7 @@
 
 import android.content.ContentUris;
 import android.os.Parcel;
+import android.platform.test.annotations.AsbSecurityTest;
 
 import androidx.test.filters.SmallTest;
 
@@ -86,6 +87,16 @@
         assertNull(u.getHost());
     }
 
+    @AsbSecurityTest(cveBugId = 261721900)
+    @SmallTest
+    public void testSchemeSanitization() {
+        Uri uri = new Uri.Builder()
+                .scheme("http://https://evil.com:/te:st/")
+                .authority("google.com").path("one/way").build();
+        assertEquals("httphttpsevil.com:/te:st/", uri.getScheme());
+        assertEquals("httphttpsevil.com:/te:st/://google.com/one/way", uri.toString());
+    }
+
     @SmallTest
     public void testStringUri() {
         assertEquals("bob lee",
diff --git a/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
index 6ae3d65..df9a89e 100644
--- a/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
+++ b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
@@ -674,8 +674,6 @@
                             protoOutputStream.write(SINGLE_INT, singleIntValue);
                             protoOutputStream.end(payloadToken);
                             protoOutputStream.end(forTestingToken);
-
-                            ctx.flush();
                         }),
                         (args) -> {}
                 );
diff --git a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
index c00ebe4..5917cc1 100644
--- a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
+++ b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
@@ -37,6 +37,7 @@
 import android.content.Context;
 import android.graphics.Insets;
 import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
 import android.view.animation.BackGestureInterpolator;
 import android.view.animation.Interpolator;
 import android.view.inputmethod.InputMethodManager;
@@ -54,6 +55,8 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.lang.reflect.Field;
+
 /**
  * Tests for {@link ImeBackAnimationController}.
  *
@@ -104,6 +107,13 @@
             when(mInsetsController.getHost()).thenReturn(mViewRootInsetsControllerHost);
             when(mViewRootInsetsControllerHost.getInputMethodManager()).thenReturn(
                     inputMethodManager);
+            try {
+                Field field = InsetsController.class.getDeclaredField("mSourceConsumers");
+                field.setAccessible(true);
+                field.set(mInsetsController, new SparseArray<InsetsSourceConsumer>());
+            } catch (NoSuchFieldException | IllegalAccessException e) {
+                throw new RuntimeException("Unable to set mSourceConsumers", e);
+            }
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -241,6 +251,23 @@
         });
     }
 
+    @Test
+    public void testOnBackInvokedHidesImeEvenIfInsetsControlCancelled() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            // start back gesture
+            WindowInsetsAnimationControlListener animationControlListener = startBackGesture();
+
+            // simulate ImeBackAnimationController not receiving control (e.g. due to split screen)
+            animationControlListener.onCancelled(mWindowInsetsAnimationController);
+
+            // commit back gesture
+            mBackAnimationController.onBackInvoked();
+
+            // verify that InsetsController#hide is called
+            verify(mInsetsController, times(1)).hide(ime());
+        });
+    }
+
     private WindowInsetsAnimationControlListener startBackGesture() {
         // start back gesture
         mBackAnimationController.onBackStarted(new BackEvent(0f, 0f, 0f, EDGE_LEFT));
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index 72f1119..0b1b40c 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
+import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
 import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
 import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
 import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
@@ -584,6 +585,44 @@
         assertEquals(0f, mViewRoot.getLastPreferredFrameRate(), 0f);
     }
 
+    @Test
+    @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+            FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY
+    })
+    public void testQuickTouchBoost() throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW);
+            ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+            layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+            layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
+            mMovingView.setLayoutParams(layoutParams);
+            mMovingView.setOnClickListener((v) -> {});
+        });
+        waitForFrameRateCategoryToSettle();
+        mActivityRule.runOnUiThread(() -> assertEquals(FRAME_RATE_CATEGORY_LOW,
+                mViewRoot.getLastPreferredFrameRateCategory()));
+        int[] position = new int[2];
+        mActivityRule.runOnUiThread(() -> {
+            mMovingView.getLocationOnScreen(position);
+            position[0] += mMovingView.getWidth() / 2;
+            position[1] += mMovingView.getHeight() / 2;
+        });
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+        long now = SystemClock.uptimeMillis();
+        MotionEvent down = MotionEvent.obtain(
+                now, // downTime
+                now, // eventTime
+                MotionEvent.ACTION_DOWN, // action
+                position[0], // x
+                position[1], // y
+                0 // metaState
+        );
+        down.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+        instrumentation.sendPointerSync(down);
+        assertEquals(FRAME_RATE_CATEGORY_HIGH_HINT, mViewRoot.getLastPreferredFrameRateCategory());
+    }
+
     private void runAfterDraw(@NonNull Runnable runnable) {
         Handler handler = new Handler(Looper.getMainLooper());
         mAfterDrawLatch = new CountDownLatch(1);
@@ -615,7 +654,6 @@
         for (int i = 0; i < 5 || mViewRoot.getIsFrameRateBoosting(); i++) {
             final CountDownLatch drawLatch = new CountDownLatch(1);
 
-            // Now that it is small, any invalidation should have a normal category
             ViewTreeObserver.OnDrawListener listener = drawLatch::countDown;
 
             mActivityRule.runOnUiThread(() -> {
@@ -627,5 +665,12 @@
             mActivityRule.runOnUiThread(
                     () -> mMovingView.getViewTreeObserver().removeOnDrawListener(listener));
         }
+        // after boosting is complete, wait for one more draw cycle to ensure the boost isn't
+        // the last frame rate set
+        mActivityRule.runOnUiThread(() -> {
+            mMovingView.invalidate();
+            runAfterDraw(() -> {});
+        });
+        waitForAfterDraw();
     }
 }
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 0b0fd66..b5c264c 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -21,7 +21,9 @@
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.accessibilityservice.IBrailleDisplayController;
 import android.accessibilityservice.MagnificationConfig;
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
+import android.annotation.RequiresNoPermission;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Region;
 import android.hardware.usb.UsbDevice;
@@ -216,16 +218,19 @@
 
     public void setAnimationScale(float scale) {}
 
+    @RequiresNoPermission
     @Override
     public void setInstalledAndEnabledServices(List<AccessibilityServiceInfo> infos)
             throws RemoteException {
     }
 
+    @RequiresNoPermission
     @Override
     public List<AccessibilityServiceInfo> getInstalledAndEnabledServices() throws RemoteException {
         return null;
     }
 
+    @RequiresNoPermission
     @Override
     public void attachAccessibilityOverlayToDisplay(
             int interactionId,
@@ -233,6 +238,7 @@
             SurfaceControl sc,
             IAccessibilityInteractionConnectionCallback callback) {}
 
+    @RequiresNoPermission
     @Override
     public void attachAccessibilityOverlayToWindow(
             int interactionId,
@@ -240,14 +246,21 @@
             SurfaceControl sc,
             IAccessibilityInteractionConnectionCallback callback) {}
 
+    @EnforcePermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @Override
-    public void connectBluetoothBrailleDisplay(String bluetoothAddress,
-            IBrailleDisplayController controller) {}
+    public void connectBluetoothBrailleDisplay(
+            String bluetoothAddress, IBrailleDisplayController controller) {
+        connectBluetoothBrailleDisplay_enforcePermission();
+    }
 
+    @RequiresNoPermission
     @Override
-    public void connectUsbBrailleDisplay(UsbDevice usbDevice,
-            IBrailleDisplayController controller) {}
+    public void connectUsbBrailleDisplay(
+            UsbDevice usbDevice, IBrailleDisplayController controller) {}
 
+    @EnforcePermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)
     @Override
-    public void setTestBrailleDisplayData(List<Bundle> brailleDisplays) {}
+    public void setTestBrailleDisplayData(List<Bundle> brailleDisplays) {
+        setTestBrailleDisplayData_enforcePermission();
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java
new file mode 100644
index 0000000..4eccbe5
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.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 com.android.internal.content.res;
+
+import static com.android.internal.content.om.OverlayConfigParser.SysPropWrapper;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.content.om.OverlayConfigParser;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class OverlayConfigParserTest {
+    @Test(expected = IllegalStateException.class)
+    public void testMergePropNotRoProp() {
+        SysPropWrapper sysProp = p -> {
+            return "dummy_value";
+        };
+        OverlayConfigParser.expandProperty("${persist.value}/path", sysProp);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testMergePropMissingEndBracket() {
+        SysPropWrapper sysProp = p -> {
+            return "dummy_value";
+        };
+        OverlayConfigParser.expandProperty("${ro.value/path", sysProp);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testMergeOnlyPropStart() {
+        SysPropWrapper sysProp = p -> {
+            return "dummy_value";
+        };
+        OverlayConfigParser.expandProperty("path/${", sysProp);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testMergePropInProp() {
+        SysPropWrapper sysProp = p -> {
+            return "dummy_value";
+        };
+        OverlayConfigParser.expandProperty("path/${${ro.value}}", sysProp);
+    }
+
+    /**
+     * The path is only allowed to contain one property.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testMergePropMultipleProps() {
+        SysPropWrapper sysProp = p -> {
+            return "dummy_value";
+        };
+        OverlayConfigParser.expandProperty("${ro.value}/path${ro.value2}/path", sysProp);
+    }
+
+    @Test
+    public void testMergePropOneProp() {
+        final SysPropWrapper sysProp = p -> {
+            if ("ro.value".equals(p)) {
+                return "dummy_value";
+            } else {
+                return "invalid";
+            }
+        };
+
+        // Property in the beginnig of the string
+        String result = OverlayConfigParser.expandProperty("${ro.value}/path",
+                sysProp);
+        assertEquals("dummy_value/path", result);
+
+        // Property in the middle of the string
+        result = OverlayConfigParser.expandProperty("path/${ro.value}/file",
+                sysProp);
+        assertEquals("path/dummy_value/file", result);
+
+        // Property at the of the string
+        result = OverlayConfigParser.expandProperty("path/${ro.value}",
+                sysProp);
+        assertEquals("path/dummy_value", result);
+
+        // Property is the entire string
+        result = OverlayConfigParser.expandProperty("${ro.value}",
+                sysProp);
+        assertEquals("dummy_value", result);
+    }
+
+    @Test
+    public void testMergePropNoProp() {
+        final SysPropWrapper sysProp = p -> {
+            return "dummy_value";
+        };
+
+        final String path = "no_props/path";
+        String result = OverlayConfigParser.expandProperty(path, sysProp);
+        assertEquals(path, result);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
index baab3b2..4846ed27 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
@@ -22,6 +22,7 @@
 import android.os.Parcel;
 import android.os.PersistableBundle;
 import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.IndentingPrintWriter;
 import android.util.SparseArray;
 import android.util.Xml;
 
@@ -31,6 +32,8 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
+import com.google.common.truth.StringSubject;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -38,6 +41,7 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.StringWriter;
 import java.nio.charset.StandardCharsets;
 
 @RunWith(AndroidJUnit4.class)
@@ -56,6 +60,9 @@
         extras.putBoolean("hasPowerMonitor", true);
         SparseArray<String> stateLabels = new SparseArray<>();
         stateLabels.put(0x0F, "idle");
+        extras.putString(PowerStats.Descriptor.EXTRA_DEVICE_STATS_FORMAT, "device:0[3]");
+        extras.putString(PowerStats.Descriptor.EXTRA_STATE_STATS_FORMAT, "state:0");
+        extras.putString(PowerStats.Descriptor.EXTRA_UID_STATS_FORMAT, "a:0 b:1");
         mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, 3, stateLabels,
                 1, 2, extras);
         mRegistry.register(mDescriptor);
@@ -191,4 +198,68 @@
         newParcel.setDataPosition(0);
         return newParcel;
     }
+
+    @Test
+    public void formatForBatteryHistory() {
+        PowerStats stats = new PowerStats(mDescriptor);
+        stats.durationMs = 1234;
+        stats.stats[0] = 10;
+        stats.stats[1] = 20;
+        stats.stats[2] = 30;
+        stats.stateStats.put(0x0F, new long[]{16});
+        stats.stateStats.put(0xF0, new long[]{17});
+        stats.uidStats.put(42, new long[]{40, 50});
+        stats.uidStats.put(99, new long[]{60, 70});
+
+        assertThat(stats.formatForBatteryHistory(" #"))
+                .isEqualTo("duration=1234 cpu="
+                        + "device: [10, 20, 30]"
+                        + " (idle) state: 16"
+                        + " (cpu-f0) state: 17"
+                        + " #42: a: 40 b: 50"
+                        + " #99: a: 60 b: 70");
+    }
+
+    @Test
+    public void dump() {
+        PowerStats stats = new PowerStats(mDescriptor);
+        stats.durationMs = 1234;
+        stats.stats[0] = 10;
+        stats.stats[1] = 20;
+        stats.stats[2] = 30;
+        stats.stateStats.put(0x0F, new long[]{16});
+        stats.stateStats.put(0xF0, new long[]{17});
+        stats.uidStats.put(42, new long[]{40, 50});
+        stats.uidStats.put(99, new long[]{60, 70});
+
+        StringWriter sw = new StringWriter();
+        IndentingPrintWriter pw = new IndentingPrintWriter(sw);
+        stats.dump(pw);
+        pw.flush();
+        String dump = sw.toString();
+
+        assertThat(dump).contains("duration=1234");
+        assertThat(dump).contains("device: [10, 20, 30]");
+        assertThat(dump).contains("(idle) state: 16");
+        assertThat(dump).contains("(cpu-f0) state: 17");
+        assertThat(dump).contains("UID 42: a: 40 b: 50");
+        assertThat(dump).contains("UID 99: a: 60 b: 70");
+    }
+
+    @Test
+    public void formatter() {
+        assertThatFormatted(new long[]{12, 34, 56}, "a:0 b:1[2]")
+                .isEqualTo("a: 12 b: [34, 56]");
+        assertThatFormatted(new long[]{12, 0, 0}, "a:0? b:1[2]?")
+                .isEqualTo("a: 12");
+        assertThatFormatted(new long[]{0, 34, 56}, "a:0? b:1[2]?")
+                .isEqualTo("b: [34, 56]");
+        assertThatFormatted(new long[]{3141592, 2000000, 1414213}, "pi:0p sqrt:1[2]p")
+                .isEqualTo("pi: 3.14 sqrt: [2.00, 1.41]");
+    }
+
+    private static StringSubject assertThatFormatted(long[] stats, String format) {
+        return assertThat(new PowerStats.PowerStatsFormatter(format)
+                .format(stats));
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/ravenwood/RavenwoodEnvironmentTest.java b/core/tests/coretests/src/com/android/internal/ravenwood/RavenwoodEnvironmentTest.java
new file mode 100644
index 0000000..d1ef61b
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/ravenwood/RavenwoodEnvironmentTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.ravenwood;
+
+import static junit.framework.TestCase.assertEquals;
+
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodEnvironmentTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    @Test
+    public void testIsRunningOnRavenwood() {
+        assertEquals(RavenwoodRule.isUnderRavenwood(),
+                RavenwoodEnvironment.getInstance().isRunningOnRavenwood());
+    }
+}
diff --git a/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java b/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java
index 1d91af5..991ada8 100644
--- a/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java
+++ b/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java
@@ -328,8 +328,11 @@
         verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
         verify(spyPackageMonitor, times(1))
                 .onPackageUpdateStarted(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID));
-
         ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(spyPackageMonitor, times(1))
+                .onPackageUpdateStartedWithExtras(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID),
+                        argumentCaptor.capture());
+
         verify(spyPackageMonitor, times(1)).onPackageDisappearedWithExtras(eq(FAKE_PACKAGE_NAME),
                 argumentCaptor.capture());
         Bundle capturedExtras = argumentCaptor.getValue();
@@ -362,11 +365,16 @@
         spyPackageMonitor.doHandlePackageEvent(intent);
 
         verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
+        ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class);
         verify(spyPackageMonitor, times(1))
                 .onPackageUpdateStarted(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID));
+        verify(spyPackageMonitor, times(1))
+                .onPackageUpdateStartedWithExtras(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID),
+                        argumentCaptor.capture());
         verify(spyPackageMonitor, times(1)).onPackageModified(eq(FAKE_PACKAGE_NAME));
+        verify(spyPackageMonitor, times(1)).onPackageModifiedWithExtras(eq(FAKE_PACKAGE_NAME),
+                argumentCaptor.capture());
 
-        ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class);
         verify(spyPackageMonitor, times(1))
                 .onPackageDisappearedWithExtras(eq(FAKE_PACKAGE_NAME), argumentCaptor.capture());
         Bundle capturedExtras = argumentCaptor.getValue();
@@ -399,12 +407,18 @@
         spyPackageMonitor.doHandlePackageEvent(intent);
 
         verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
+        ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class);
         verify(spyPackageMonitor, times(1))
                 .onPackageRemoved(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID));
         verify(spyPackageMonitor, times(1))
+                .onPackageRemovedWithExtras(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID),
+                        argumentCaptor.capture());
+        verify(spyPackageMonitor, times(1))
                 .onPackageRemovedAllUsers(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID));
+        verify(spyPackageMonitor, times(1))
+                .onPackageRemovedAllUsersWithExtras(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID),
+                        argumentCaptor.capture());
 
-        ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class);
         verify(spyPackageMonitor, times(1)).onPackageDisappearedWithExtras(eq(FAKE_PACKAGE_NAME),
                 argumentCaptor.capture());
         Bundle capturedExtras = argumentCaptor.getValue();
@@ -436,11 +450,16 @@
         spyPackageMonitor.doHandlePackageEvent(intent);
 
         verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
+        ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class);
         verify(spyPackageMonitor, times(1))
                 .onPackageUpdateFinished(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID));
+        verify(spyPackageMonitor, times(1))
+                .onPackageModifiedWithExtras(eq(FAKE_PACKAGE_NAME), argumentCaptor.capture());
         verify(spyPackageMonitor, times(1)).onPackageModified(eq(FAKE_PACKAGE_NAME));
+        verify(spyPackageMonitor, times(1))
+                .onPackageModifiedWithExtras(eq(FAKE_PACKAGE_NAME), argumentCaptor.capture());
 
-        ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class);
+
         verify(spyPackageMonitor, times(1)).onPackageAppearedWithExtras(eq(FAKE_PACKAGE_NAME),
                 argumentCaptor.capture());
         Bundle capturedExtras = argumentCaptor.getValue();
@@ -472,8 +491,11 @@
         verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
         verify(spyPackageMonitor, times(1))
                 .onPackageAdded(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID));
-
         ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(spyPackageMonitor, times(1))
+                .onPackageAddedWithExtras(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID),
+                        argumentCaptor.capture());
+
         verify(spyPackageMonitor, times(1)).onPackageAppearedWithExtras(eq(FAKE_PACKAGE_NAME),
                 argumentCaptor.capture());
         Bundle capturedExtras = argumentCaptor.getValue();
diff --git a/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java b/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java
new file mode 100644
index 0000000..bcdac61
--- /dev/null
+++ b/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.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 com.android.internal.util;
+
+import static junit.framework.Assert.assertEquals;
+
+
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for {@link NewlineNormalizer}
+ * @hide
+ */
+@DisabledOnRavenwood(blockedBy = NewlineNormalizer.class)
+@RunWith(AndroidJUnit4.class)
+public class NewlineNormalizerTest {
+
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    @Test
+    public void testEmptyInput() {
+        assertEquals("", NewlineNormalizer.normalizeNewlines(""));
+    }
+
+    @Test
+    public void testSingleNewline() {
+        assertEquals("\n", NewlineNormalizer.normalizeNewlines("\n"));
+    }
+
+    @Test
+    public void testMultipleConsecutiveNewlines() {
+        assertEquals("\n", NewlineNormalizer.normalizeNewlines("\n\n\n\n\n"));
+    }
+
+    @Test
+    public void testNewlinesWithSpacesAndTabs() {
+        String input = "Line 1\n  \n \t \n\tLine 2";
+        // Adjusted expected output to include the tab character
+        String expected = "Line 1\n\tLine 2";
+        assertEquals(expected, NewlineNormalizer.normalizeNewlines(input));
+    }
+
+    @Test
+    public void testMixedNewlineCharacters() {
+        String input = "Line 1\r\nLine 2\u000BLine 3\fLine 4\u2028Line 5\u2029Line 6";
+        String expected = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6";
+        assertEquals(expected, NewlineNormalizer.normalizeNewlines(input));
+    }
+}
diff --git a/data/etc/core.protolog.pb b/data/etc/core.protolog.pb
index 000f6ef..b41a607 100644
--- a/data/etc/core.protolog.pb
+++ b/data/etc/core.protolog.pb
Binary files differ
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 01deb49..b93cd46 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -583,6 +583,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "7211222997110112110": {
+      "message": "Refreshing activity for freeform camera compatibility treatment, activityRecord=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/ActivityRefresher.java"
+    },
     "1665699123574159131": {
       "message": "Starting activity when config will change = %b",
       "level": "VERBOSE",
@@ -1771,12 +1777,6 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
     },
-    "-7756685416834187936": {
-      "message": "Refreshing activity for camera compatibility treatment, activityRecord=%s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
-    },
     "-5176775281239247368": {
       "message": "Reverting orientation after camera compat force rotation",
       "level": "VERBOSE",
diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml
index ac1b064..a4ee825 100644
--- a/data/fonts/font_fallback_cjkvf.xml
+++ b/data/fonts/font_fallback_cjkvf.xml
@@ -768,7 +768,7 @@
         <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
     </family>
     <family lang="zh-Hans">
-        <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"
+        <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"
             supportedAxes="wght">
             NotoSansCJK-Regular.ttc
             <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
@@ -780,7 +780,7 @@
         </font>
     </family>
     <family lang="zh-Hant,zh-Bopo">
-        <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"
+        <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"
             supportedAxes="wght">
             NotoSansCJK-Regular.ttc
             <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
@@ -792,7 +792,7 @@
         </font>
     </family>
     <family lang="ja">
-        <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"
+        <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"
             supportedAxes="wght">
             NotoSansCJK-Regular.ttc
             <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
@@ -810,7 +810,7 @@
         </font>
     </family>
     <family lang="ko">
-        <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"
+        <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"
             supportedAxes="wght">
             NotoSansCJK-Regular.ttc
             <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400
diff --git a/data/fonts/fonts_cjkvf.xml b/data/fonts/fonts_cjkvf.xml
index 9545ae7..8cbc300 100644
--- a/data/fonts/fonts_cjkvf.xml
+++ b/data/fonts/fonts_cjkvf.xml
@@ -1409,39 +1409,39 @@
         <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
     </family>
     <family lang="zh-Hans">
-        <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="100"/>
         </font>
-        <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="200"/>
         </font>
-        <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="300"/>
         </font>
-        <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="500"/>
         </font>
-        <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="600"/>
         </font>
-        <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="700"/>
         </font>
-        <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="800"/>
         </font>
-        <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="900"/>
         </font>
@@ -1450,39 +1450,39 @@
         </font>
     </family>
     <family lang="zh-Hant,zh-Bopo">
-        <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="100"/>
         </font>
-        <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="200"/>
         </font>
-        <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="300"/>
         </font>
-        <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="500"/>
         </font>
-        <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="600"/>
         </font>
-        <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="700"/>
         </font>
-        <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="800"/>
         </font>
-        <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="900"/>
         </font>
@@ -1491,39 +1491,39 @@
         </font>
     </family>
     <family lang="ja">
-        <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="100"/>
         </font>
-        <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="200"/>
         </font>
-        <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="300"/>
         </font>
-        <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="500"/>
         </font>
-        <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="600"/>
         </font>
-        <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="700"/>
         </font>
-        <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="800"/>
         </font>
-        <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="900"/>
         </font>
@@ -1542,39 +1542,39 @@
         </font>
     </family>
     <family lang="ko">
-        <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="100"/>
         </font>
-        <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="200"/>
         </font>
-        <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="300"/>
         </font>
-        <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="500"/>
         </font>
-        <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="600"/>
         </font>
-        <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="700"/>
         </font>
-        <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="800"/>
         </font>
-        <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+        <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular">
             NotoSansCJK-Regular.ttc
             <axis tag="wght" stylevalue="900"/>
         </font>
diff --git a/data/keyboards/Android.bp b/data/keyboards/Android.bp
index e62678f..423b55b 100644
--- a/data/keyboards/Android.bp
+++ b/data/keyboards/Android.bp
@@ -33,7 +33,7 @@
     srcs: [
         "*.kl",
     ],
-    installable: false,
+    no_full_install: true,
 }
 
 prebuilt_usr_keychars {
@@ -41,7 +41,7 @@
     srcs: [
         "*.kcm",
     ],
-    installable: false,
+    no_full_install: true,
 }
 
 prebuilt_usr_idc {
@@ -49,5 +49,5 @@
     srcs: [
         "*.idc",
     ],
-    installable: false,
+    no_full_install: true,
 }
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index e8b4104..f8d3bff 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -438,9 +438,15 @@
 key usage 0x0c0079 KEYBOARD_BACKLIGHT_UP     FALLBACK_USAGE_MAPPING
 key usage 0x0c007A KEYBOARD_BACKLIGHT_DOWN   FALLBACK_USAGE_MAPPING
 key usage 0x0c007C KEYBOARD_BACKLIGHT_TOGGLE FALLBACK_USAGE_MAPPING
+key usage 0x0c00D9 EMOJI_PICKER              FALLBACK_USAGE_MAPPING
 key usage 0x0c0173 MEDIA_AUDIO_TRACK         FALLBACK_USAGE_MAPPING
 key usage 0x0c019C PROFILE_SWITCH            FALLBACK_USAGE_MAPPING
+key usage 0x0c019F SETTINGS                  FALLBACK_USAGE_MAPPING
 key usage 0x0c01A2 ALL_APPS                  FALLBACK_USAGE_MAPPING
+key usage 0x0c0227 REFRESH                   FALLBACK_USAGE_MAPPING
+key usage 0x0c029D LANGUAGE_SWITCH           FALLBACK_USAGE_MAPPING
+key usage 0x0c029F RECENT_APPS               FALLBACK_USAGE_MAPPING
+key usage 0x0c02A2 ALL_APPS                  FALLBACK_USAGE_MAPPING
 key usage 0x0d0044 STYLUS_BUTTON_PRIMARY     FALLBACK_USAGE_MAPPING
 key usage 0x0d005a STYLUS_BUTTON_SECONDARY   FALLBACK_USAGE_MAPPING
 
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index d915b74..1c20141 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -143,7 +143,7 @@
          * the decoder will try to pick the best matching config based on the
          * system's screen depth, and characteristics of the original image such
          * as if it has per-pixel alpha (requiring a config that also does).
-         * 
+         *
          * Image are loaded with the {@link Bitmap.Config#ARGB_8888} config by
          * default.
          */
@@ -183,7 +183,7 @@
 
         /**
          * If true (which is the default), the resulting bitmap will have its
-         * color channels pre-multipled by the alpha channel.
+         * color channels pre-multiplied by the alpha channel.
          *
          * <p>This should NOT be set to false for images to be directly drawn by
          * the view system or through a {@link Canvas}. The view system and
@@ -221,9 +221,9 @@
          * if {@link #inScaled} is set (which it is by default} and this
          * density does not match {@link #inTargetDensity}, then the bitmap
          * will be scaled to the target density before being returned.
-         * 
+         *
          * <p>If this is 0,
-         * {@link BitmapFactory#decodeResource(Resources, int)}, 
+         * {@link BitmapFactory#decodeResource(Resources, int)},
          * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)},
          * and {@link BitmapFactory#decodeResourceStream}
          * will fill in the density associated with the resource.  The other
@@ -242,29 +242,29 @@
          * This is used in conjunction with {@link #inDensity} and
          * {@link #inScaled} to determine if and how to scale the bitmap before
          * returning it.
-         * 
+         *
          * <p>If this is 0,
-         * {@link BitmapFactory#decodeResource(Resources, int)}, 
+         * {@link BitmapFactory#decodeResource(Resources, int)},
          * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)},
          * and {@link BitmapFactory#decodeResourceStream}
          * will fill in the density associated the Resources object's
          * DisplayMetrics.  The other
          * functions will leave it as-is and no scaling for density will be
          * performed.
-         * 
+         *
          * @see #inDensity
          * @see #inScreenDensity
          * @see #inScaled
          * @see android.util.DisplayMetrics#densityDpi
          */
         public int inTargetDensity;
-        
+
         /**
          * The pixel density of the actual screen that is being used.  This is
          * purely for applications running in density compatibility code, where
          * {@link #inTargetDensity} is actually the density the application
          * sees rather than the real screen density.
-         * 
+         *
          * <p>By setting this, you
          * allow the loading code to avoid scaling a bitmap that is currently
          * in the screen density up/down to the compatibility density.  Instead,
@@ -274,18 +274,18 @@
          * Bitmap.getScaledWidth} and {@link Bitmap#getScaledHeight
          * Bitmap.getScaledHeight} to account for any different between the
          * bitmap's density and the target's density.
-         * 
+         *
          * <p>This is never set automatically for the caller by
          * {@link BitmapFactory} itself.  It must be explicitly set, since the
          * caller must deal with the resulting bitmap in a density-aware way.
-         * 
+         *
          * @see #inDensity
          * @see #inTargetDensity
          * @see #inScaled
          * @see android.util.DisplayMetrics#densityDpi
          */
         public int inScreenDensity;
-        
+
         /**
          * When this flag is set, if {@link #inDensity} and
          * {@link #inTargetDensity} are not 0, the
@@ -345,7 +345,7 @@
          * ignored.
          *
          * In {@link android.os.Build.VERSION_CODES#KITKAT} and below, this
-         * field works in conjuction with inPurgeable. If inPurgeable is false,
+         * field works in conjunction with inPurgeable. If inPurgeable is false,
          * then this field is ignored. If inPurgeable is true, then this field
          * determines whether the bitmap can share a reference to the input
          * data (inputstream, array, etc.) or if it must make a deep copy.
@@ -583,11 +583,11 @@
                 opts.inDensity = density;
             }
         }
-        
+
         if (opts.inTargetDensity == 0 && res != null) {
             opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
         }
-        
+
         return decodeStream(is, pad, opts);
     }
 
@@ -611,8 +611,8 @@
     public static Bitmap decodeResource(Resources res, int id, Options opts) {
         validate(opts);
         Bitmap bm = null;
-        InputStream is = null; 
-        
+        InputStream is = null;
+
         try {
             final TypedValue value = new TypedValue();
             is = res.openRawResource(id, value);
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index e03a1da..0b3e545 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -55,7 +55,7 @@
  * Canvas and Drawables</a> developer guide.</p></div>
  */
 public class Canvas extends BaseCanvas {
-    private static int sCompatiblityVersion = 0;
+    private static int sCompatibilityVersion = 0;
     private static boolean sCompatibilityRestore = false;
     private static boolean sCompatibilitySetBitmap = false;
 
@@ -74,7 +74,7 @@
 
     // Maximum bitmap size as defined in Skia's native code
     // (see SkCanvas.cpp, SkDraw.cpp)
-    private static final int MAXMIMUM_BITMAP_SIZE = 32766;
+    private static final int MAXIMUM_BITMAP_SIZE = 32766;
 
     // Use a Holder to allow static initialization of Canvas in the boot image.
     private static class NoImagePreloadHolder {
@@ -331,7 +331,7 @@
      * @see #getMaximumBitmapHeight()
      */
     public int getMaximumBitmapWidth() {
-        return MAXMIMUM_BITMAP_SIZE;
+        return MAXIMUM_BITMAP_SIZE;
     }
 
     /**
@@ -342,7 +342,7 @@
      * @see #getMaximumBitmapWidth()
      */
     public int getMaximumBitmapHeight() {
-        return MAXMIMUM_BITMAP_SIZE;
+        return MAXIMUM_BITMAP_SIZE;
     }
 
     // the SAVE_FLAG constants must match their native equivalents
@@ -423,7 +423,7 @@
     public static final int ALL_SAVE_FLAG = 0x1F;
 
     private static void checkValidSaveFlags(int saveFlags) {
-        if (sCompatiblityVersion >= Build.VERSION_CODES.P
+        if (sCompatibilityVersion >= Build.VERSION_CODES.P
                 && saveFlags != ALL_SAVE_FLAG) {
             throw new IllegalArgumentException(
                     "Invalid Layer Save Flag - only ALL_SAVE_FLAGS is allowed");
@@ -845,7 +845,7 @@
     }
 
     private static void checkValidClipOp(@NonNull Region.Op op) {
-        if (sCompatiblityVersion >= Build.VERSION_CODES.P
+        if (sCompatibilityVersion >= Build.VERSION_CODES.P
                 && op != Region.Op.INTERSECT && op != Region.Op.DIFFERENCE) {
             throw new IllegalArgumentException(
                     "Invalid Region.Op - only INTERSECT and DIFFERENCE are allowed");
@@ -1435,7 +1435,7 @@
     }
 
     /*package*/ static void setCompatibilityVersion(int apiLevel) {
-        sCompatiblityVersion = apiLevel;
+        sCompatibilityVersion = apiLevel;
         sCompatibilityRestore = apiLevel < Build.VERSION_CODES.M;
         sCompatibilitySetBitmap = apiLevel < Build.VERSION_CODES.O;
         nSetCompatibilityVersion(apiLevel);
diff --git a/graphics/java/android/graphics/Color.java b/graphics/java/android/graphics/Color.java
index 0f2f879..c1edafc 100644
--- a/graphics/java/android/graphics/Color.java
+++ b/graphics/java/android/graphics/Color.java
@@ -289,6 +289,9 @@
  */
 @AnyThread
 @SuppressAutoDoc
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodClassLoadHook(
+        android.ravenwood.annotation.RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK)
 public class Color {
     @ColorInt public static final int BLACK       = 0xFF000000;
     @ColorInt public static final int DKGRAY      = 0xFF444444;
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index a2319a5..4bc3ece 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -135,6 +135,9 @@
 @AnyThread
 @SuppressWarnings("StaticInitializerReferencesSubClass")
 @SuppressAutoDoc
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodClassLoadHook(
+        android.ravenwood.annotation.RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK)
 public abstract class ColorSpace {
     /**
      * Standard CIE 1931 2° illuminant A, encoded in xyY.
@@ -2490,9 +2493,16 @@
             return mNativePtr;
         }
 
-        private static native long nativeGetNativeFinalizer();
-        private static native long nativeCreate(float a, float b, float c, float d,
-                float e, float f, float g, float[] xyz);
+        /**
+         * These methods can't be put in the Rgb class directly, because ColorSpace's
+         * static initializer instantiates Rgb, whose constructor needs them, which is a variation
+         * of b/337329128.
+         */
+        static class Native {
+            static native long nativeGetNativeFinalizer();
+            static native long nativeCreate(float a, float b, float c, float d,
+                    float e, float f, float g, float[] xyz);
+        }
 
         private static DoubleUnaryOperator generateOETF(TransferParameters function) {
             if (function.isHLGish()) {
@@ -2959,7 +2969,7 @@
 
                 // This mimics the old code that was in native.
                 float[] nativeTransform = adaptToIlluminantD50(mWhitePoint, mTransform);
-                mNativePtr = nativeCreate((float) mTransferParameters.a,
+                mNativePtr = Native.nativeCreate((float) mTransferParameters.a,
                                           (float) mTransferParameters.b,
                                           (float) mTransferParameters.c,
                                           (float) mTransferParameters.d,
@@ -2975,7 +2985,7 @@
 
         private static class NoImagePreloadHolder {
             public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
-                ColorSpace.Rgb.class.getClassLoader(), nativeGetNativeFinalizer(), 0);
+                ColorSpace.Rgb.class.getClassLoader(), Native.nativeGetNativeFinalizer(), 0);
         }
 
         /**
diff --git a/graphics/java/android/graphics/ComposePathEffect.java b/graphics/java/android/graphics/ComposePathEffect.java
index 3fc9eb5..7d59ece 100644
--- a/graphics/java/android/graphics/ComposePathEffect.java
+++ b/graphics/java/android/graphics/ComposePathEffect.java
@@ -20,13 +20,13 @@
 
     /**
      * Construct a PathEffect whose effect is to apply first the inner effect
-     * and the the outer pathEffect (e.g. outer(inner(path))).
+     * and the outer pathEffect (e.g. outer(inner(path))).
      */
     public ComposePathEffect(PathEffect outerpe, PathEffect innerpe) {
         native_instance = nativeCreate(outerpe.native_instance,
                                        innerpe.native_instance);
     }
-    
+
     private static native long nativeCreate(long nativeOuterpe,
                                             long nativeInnerpe);
 }
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index c86b744..88f0e8e 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -219,7 +219,7 @@
 
     @CriticalNative
     private static native long nGetFamilyReleaseFunc();
-    // By passing -1 to weigth argument, the weight value is resolved by OS/2 table in the font.
+    // By passing -1 to weight argument, the weight value is resolved by OS/2 table in the font.
     // By passing -1 to italic argument, the italic value is resolved by OS/2 table in the font.
     private static native boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex,
             int weight, int isItalic);
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 17c2dd9..13c4a94 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -127,7 +127,7 @@
             parser.setInput(is, null);
             parser.nextTag();
             return readFamilies(parser, systemFontDir, oemCustomization, updatableFontMap,
-                    lastModifiedDate, configVersion, false /* filter out the non-exising files */);
+                    lastModifiedDate, configVersion, false /* filter out the non-existing files */);
         }
     }
 
@@ -254,7 +254,7 @@
      * @param parser An XML parser.
      * @param fontDir a font directory name.
      * @param updatableFontMap a updated font file map.
-     * @param allowNonExistingFile true to allow font file that doesn't exists
+     * @param allowNonExistingFile true to allow font file that doesn't exist.
      * @return a FontFamily instance. null if no font files are available in this FontFamily.
      */
     public static @Nullable FontConfig.FontFamily readFamily(XmlPullParser parser, String fontDir,
diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java
index b3615ff..8f12828 100644
--- a/graphics/java/android/graphics/FrameInfo.java
+++ b/graphics/java/android/graphics/FrameInfo.java
@@ -24,7 +24,7 @@
 /**
  * Class that contains all the timing information for the current frame. This
  * is used in conjunction with the hardware renderer to provide
- * continous-monitoring jank events
+ * continuous-monitoring jank events
  *
  * All times in nanoseconds from CLOCK_MONOTONIC/System.nanoTime()
  *
diff --git a/graphics/java/android/graphics/Interpolator.java b/graphics/java/android/graphics/Interpolator.java
index 994fb2d..28f296d 100644
--- a/graphics/java/android/graphics/Interpolator.java
+++ b/graphics/java/android/graphics/Interpolator.java
@@ -28,13 +28,13 @@
         mFrameCount = 2;
         native_instance = nativeConstructor(valueCount, 2);
     }
-    
+
     public Interpolator(int valueCount, int frameCount) {
         mValueCount = valueCount;
         mFrameCount = frameCount;
         native_instance = nativeConstructor(valueCount, frameCount);
     }
-    
+
     /**
      * Reset the Interpolator to have the specified number of values and an
      * implicit keyFrame count of 2 (just a start and end). After this call the
@@ -43,7 +43,7 @@
     public void reset(int valueCount) {
         reset(valueCount, 2);
     }
-    
+
     /**
      * Reset the Interpolator to have the specified number of values and
      * keyFrames. After this call the values for each keyFrame must be assigned
@@ -54,20 +54,20 @@
         mFrameCount = frameCount;
         nativeReset(native_instance, valueCount, frameCount);
     }
-    
+
     public final int getKeyFrameCount() {
         return mFrameCount;
     }
-    
+
     public final int getValueCount() {
         return mValueCount;
     }
-    
+
     /**
      * Assign the keyFrame (specified by index) a time value and an array of key
-     * values (with an implicity blend array of [0, 0, 1, 1] giving linear
+     * values (with an implicitly blend array of [0, 0, 1, 1] giving linear
      * transition to the next set of key values).
-     * 
+     *
      * @param index The index of the key frame to assign
      * @param msec The time (in mililiseconds) for this key frame. Based on the
      *        SystemClock.uptimeMillis() clock
@@ -80,7 +80,7 @@
     /**
      * Assign the keyFrame (specified by index) a time value and an array of key
      * values and blend array.
-     * 
+     *
      * @param index The index of the key frame to assign
      * @param msec The time (in mililiseconds) for this key frame. Based on the
      *        SystemClock.uptimeMillis() clock
@@ -99,7 +99,7 @@
         }
         nativeSetKeyFrame(native_instance, index, msec, values, blend);
     }
-    
+
     /**
      * Set a repeat count (which may be fractional) for the interpolator, and
      * whether the interpolator should mirror its repeats. The default settings
@@ -110,7 +110,7 @@
             nativeSetRepeatMirror(native_instance, repeatCount, mirror);
         }
     }
-    
+
     public enum Result {
         NORMAL,
         FREEZE_START,
@@ -130,7 +130,7 @@
      * return whether the specified time was within the range of key times
      * (NORMAL), was before the first key time (FREEZE_START) or after the last
      * key time (FREEZE_END). In any event, computed values are always returned.
-     * 
+     *
      * @param msec The time (in milliseconds) used to sample into the
      *        Interpolator. Based on the SystemClock.uptimeMillis() clock
      * @param values Where to write the computed values (may be NULL).
@@ -146,13 +146,13 @@
             default: return Result.FREEZE_END;
         }
     }
-    
+
     @Override
     protected void finalize() throws Throwable {
         nativeDestructor(native_instance);
         native_instance = 0;  // Other finalizers can still call us.
     }
-    
+
     private int mValueCount;
     private int mFrameCount;
     private long native_instance;
diff --git a/graphics/java/android/graphics/LightingColorFilter.java b/graphics/java/android/graphics/LightingColorFilter.java
index 0aa6f12..fe73a1a 100644
--- a/graphics/java/android/graphics/LightingColorFilter.java
+++ b/graphics/java/android/graphics/LightingColorFilter.java
@@ -16,8 +16,8 @@
 
 // This file was generated from the C++ include file: SkColorFilter.h
 // Any changes made to this file will be discarded by the build.
-// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, 
-// or one of the auxilary file specifications in device/tools/gluemaker.
+// To change this file, either edit the include, or device/tools/gluemaker/main.cpp,
+// or one of the auxiliary file specifications in device/tools/gluemaker.
 
 package android.graphics;
 
diff --git a/graphics/java/android/graphics/LinearGradient.java b/graphics/java/android/graphics/LinearGradient.java
index 56d912b..0879371 100644
--- a/graphics/java/android/graphics/LinearGradient.java
+++ b/graphics/java/android/graphics/LinearGradient.java
@@ -63,7 +63,7 @@
      * @param colors       The sRGB colors to be distributed along the gradient line
      * @param positions    May be null. The relative positions [0..1] of
      *                     each corresponding color in the colors array. If this is null,
-     *                     the the colors are distributed evenly along the gradient line.
+     *                     the colors are distributed evenly along the gradient line.
      * @param tile         The Shader tiling mode
      */
     public LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorInt int[] colors,
@@ -82,7 +82,7 @@
      * @param colors       The colors to be distributed along the gradient line
      * @param positions    May be null. The relative positions [0..1] of
      *                     each corresponding color in the colors array. If this is null,
-     *                     the the colors are distributed evenly along the gradient line.
+     *                     the colors are distributed evenly along the gradient line.
      * @param tile         The Shader tiling mode
      *
      * @throws IllegalArgumentException if there are less than two colors, the colors do
diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java
index fbb690c..748e9dd 100644
--- a/graphics/java/android/graphics/Matrix.java
+++ b/graphics/java/android/graphics/Matrix.java
@@ -232,7 +232,7 @@
     private static class NoImagePreloadHolder {
         public static final NativeAllocationRegistry sRegistry =
                 NativeAllocationRegistry.createMalloced(
-                Matrix.class.getClassLoader(), nGetNativeFinalizerWrapper());
+                Matrix.class.getClassLoader(), ExtraNatives.nGetNativeFinalizer());
     }
 
     private final long native_instance;
@@ -241,7 +241,7 @@
      * Create an identity matrix
      */
     public Matrix() {
-        native_instance = nCreateWrapper(0);
+        native_instance = ExtraNatives.nCreate(0);
         NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance);
     }
 
@@ -251,7 +251,7 @@
      * @param src The matrix to copy into this matrix
      */
     public Matrix(Matrix src) {
-        native_instance = nCreateWrapper(src != null ? src.native_instance : 0);
+        native_instance = ExtraNatives.nCreate(src != null ? src.native_instance : 0);
         NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance);
     }
 
@@ -562,7 +562,7 @@
 
     /**
      * Set the matrix to the scale and translate values that map the source rectangle to the
-     * destination rectangle, returning true if the the result can be represented.
+     * destination rectangle, returning true if the result can be represented.
      *
      * @param src the source rectangle to map from.
      * @param dst the destination rectangle to map to.
@@ -849,40 +849,6 @@
         return native_instance;
     }
 
-    /**
-     * Wrapper method we use to switch to ExtraNatives.nCreate(src) only on Ravenwood.
-     *
-     * @see ExtraNatives
-     */
-    @android.ravenwood.annotation.RavenwoodReplace
-    private static long nCreateWrapper(long src) {
-        return nCreate(src);
-    }
-
-    private static long nCreateWrapper$ravenwood(long src) {
-        return ExtraNatives.nCreate(src);
-    }
-
-    /**
-     * Wrapper method we use to switch to ExtraNatives.nGetNativeFinalizer(src) only on Ravenwood.
-     *
-     * @see ExtraNatives
-     */
-    @android.ravenwood.annotation.RavenwoodReplace
-    private static long nGetNativeFinalizerWrapper() {
-        return nGetNativeFinalizer();
-    }
-
-    private static long nGetNativeFinalizerWrapper$ravenwood() {
-        return ExtraNatives.nGetNativeFinalizer();
-    }
-
-    // ------------------ Regular JNI ------------------------
-
-    private static native long nCreate(long nSrc_or_zero);
-    private static native long nGetNativeFinalizer();
-
-
     // ------------------ Fast JNI ------------------------
 
     @FastNative
@@ -982,14 +948,6 @@
      * There are two methods that are called by the static initializers (either directly or
      * indirectly) in this class, namely nCreate() and nGetNativeFinalizer(). On Ravenwood
      * these methods can't be on the Matrix class itself, so we use a nested class to host them.
-     *
-     * We still keep the original nCreate() method and call it on non-ravenwood environment,
-     * in order to avoid problems in downstream (such as Android Studio).
-     *
-     * @see #nCreateWrapper(long)
-     * @see #nGetNativeFinalizerWrapper()
-     *
-     * TODO(b/337110712) Clean it up somehow. (remove the original nCreate() and unify the code?)
      */
     private static class ExtraNatives {
         static native long nCreate(long nSrc_or_zero);
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java
index a4bce9e..6be8332 100644
--- a/graphics/java/android/graphics/Mesh.java
+++ b/graphics/java/android/graphics/Mesh.java
@@ -271,7 +271,7 @@
      * not have a uniform with that name or if the uniform is declared with a type other than int
      * or int[1] then an IllegalArgumentException is thrown.
      *
-     * @param uniformName name matching the int uniform delcared in the shader program.
+     * @param uniformName name matching the int uniform declared in the shader program.
      * @param value       value corresponding to the int uniform with the given name.
      */
     public void setIntUniform(@NonNull String uniformName, int value) {
@@ -283,7 +283,7 @@
      * not have a uniform with that name or if the uniform is declared with a type other than ivec2
      * or int[2] then an IllegalArgumentException is thrown.
      *
-     * @param uniformName name matching the int uniform delcared in the shader program.
+     * @param uniformName name matching the int uniform declared in the shader program.
      * @param value1      first value corresponding to the int uniform with the given name.
      * @param value2      second value corresponding to the int uniform with the given name.
      */
@@ -296,7 +296,7 @@
      * not have a uniform with that name or if the uniform is declared with a type other than ivec3
      * or int[3] then an IllegalArgumentException is thrown.
      *
-     * @param uniformName name matching the int uniform delcared in the shader program.
+     * @param uniformName name matching the int uniform declared in the shader program.
      * @param value1      first value corresponding to the int uniform with the given name.
      * @param value2      second value corresponding to the int uniform with the given name.
      * @param value3      third value corresponding to the int uniform with the given name.
@@ -310,7 +310,7 @@
      * not have a uniform with that name or if the uniform is declared with a type other than ivec4
      * or int[4] then an IllegalArgumentException is thrown.
      *
-     * @param uniformName name matching the int uniform delcared in the shader program.
+     * @param uniformName name matching the int uniform declared in the shader program.
      * @param value1      first value corresponding to the int uniform with the given name.
      * @param value2      second value corresponding to the int uniform with the given name.
      * @param value3      third value corresponding to the int uniform with the given name.
@@ -327,7 +327,7 @@
      * int (for N=1), ivecN, or int[N], where N is the length of the values param, then an
      * IllegalArgumentException is thrown.
      *
-     * @param uniformName name matching the int uniform delcared in the shader program.
+     * @param uniformName name matching the int uniform declared in the shader program.
      * @param values      int values corresponding to the vec4 int uniform with the given name.
      */
     public void setIntUniform(@NonNull String uniformName, @NonNull int[] values) {
diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java
index af20957..382269f 100644
--- a/graphics/java/android/graphics/NinePatch.java
+++ b/graphics/java/android/graphics/NinePatch.java
@@ -22,12 +22,12 @@
  * The NinePatch class permits drawing a bitmap in nine or more sections.
  * Essentially, it allows the creation of custom graphics that will scale the
  * way that you define, when content added within the image exceeds the normal
- * bounds of the graphic. For a thorough explanation of a NinePatch image, 
- * read the discussion in the 
+ * bounds of the graphic. For a thorough explanation of a NinePatch image,
+ * read the discussion in the
  * <a href="{@docRoot}guide/topics/graphics/2d-graphics.html#nine-patch">2D
  * Graphics</a> document.
  * <p>
- * The <a href="{@docRoot}guide/developing/tools/draw9patch.html">Draw 9-Patch</a> 
+ * The <a href="{@docRoot}guide/developing/tools/draw9patch.html">Draw 9-Patch</a>
  * tool offers an extremely handy way to create your NinePatch images,
  * using a WYSIWYG graphics editor.
  * </p>
@@ -104,7 +104,7 @@
         this(bitmap, chunk, null);
     }
 
-    /** 
+    /**
      * Create a drawable projection from a bitmap to nine patches.
      *
      * @param bitmap The bitmap describing the patches.
@@ -122,7 +122,7 @@
     protected void finalize() throws Throwable {
         try {
             if (mNativeChunk != 0) {
-                // only attempt to destroy correctly initilized chunks
+                // only attempt to destroy correctly initialized chunks
                 nativeFinalize(mNativeChunk);
                 mNativeChunk = 0;
             }
@@ -169,8 +169,8 @@
     public Bitmap getBitmap() {
         return mBitmap;
     }
-    
-    /** 
+
+    /**
      * Draws the NinePatch. This method will use the paint returned by {@link #getPaint()}.
      *
      * @param canvas A container for the current matrix and clip used to draw the NinePatch.
@@ -180,7 +180,7 @@
         canvas.drawPatch(this, location, mPaint);
     }
 
-    /** 
+    /**
      * Draws the NinePatch. This method will use the paint returned by {@link #getPaint()}.
      *
      * @param canvas A container for the current matrix and clip used to draw the NinePatch.
@@ -190,7 +190,7 @@
         canvas.drawPatch(this, location, mPaint);
     }
 
-    /** 
+    /**
      * Draws the NinePatch. This method will ignore the paint returned
      * by {@link #getPaint()} and use the specified paint instead.
      *
diff --git a/graphics/java/android/graphics/PathMeasure.java b/graphics/java/android/graphics/PathMeasure.java
index 5500c52..2c6cfa5 100644
--- a/graphics/java/android/graphics/PathMeasure.java
+++ b/graphics/java/android/graphics/PathMeasure.java
@@ -25,14 +25,14 @@
      * setPath.
      *
      * Note that once a path is associated with the measure object, it is
-     * undefined if the path is subsequently modified and the the measure object
+     * undefined if the path is subsequently modified and the measure object
      * is used. If the path is modified, you must call setPath with the path.
      */
     public PathMeasure() {
         mPath = null;
         native_instance = native_create(0, false);
     }
-    
+
     /**
      * Create a PathMeasure object associated with the specified path object
      * (already created and specified). The measure object can now return the
@@ -40,7 +40,7 @@
      * path.
      *
      * Note that once a path is associated with the measure object, it is
-     * undefined if the path is subsequently modified and the the measure object
+     * undefined if the path is subsequently modified and the measure object
      * is used. If the path is modified, you must call setPath with the path.
      *
      * @param path The path that will be measured by this object
@@ -121,7 +121,7 @@
      * such as <code>dst.rLineTo(0, 0)</code>.</p>
      */
     public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) {
-        // Skia used to enforce this as part of it's API, but has since relaxed that restriction
+        // Skia used to enforce this as part of its API, but has since relaxed that restriction
         // so to maintain consistency in our API we enforce the preconditions here.
         float length = getLength();
         if (startD < 0) {
diff --git a/graphics/java/android/graphics/Rasterizer.java b/graphics/java/android/graphics/Rasterizer.java
index 29d82fa..5750954 100644
--- a/graphics/java/android/graphics/Rasterizer.java
+++ b/graphics/java/android/graphics/Rasterizer.java
@@ -16,8 +16,8 @@
 
 // This file was generated from the C++ include file: SkRasterizer.h
 // Any changes made to this file will be discarded by the build.
-// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, 
-// or one of the auxilary file specifications in device/tools/gluemaker.
+// To change this file, either edit the include, or device/tools/gluemaker/main.cpp,
+// or one of the auxiliary file specifications in device/tools/gluemaker.
 
 package android.graphics;
 
diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java
index 635e78e..cc5b3b9 100644
--- a/graphics/java/android/graphics/RecordingCanvas.java
+++ b/graphics/java/android/graphics/RecordingCanvas.java
@@ -40,7 +40,7 @@
 
     /** @hide */
     private static int getPanelFrameSize() {
-        final int DefaultSize = 100 * 1024 * 1024; // 100 MB;
+        final int DefaultSize = 150 * 1024 * 1024; // 150 MB;
         return Math.max(SystemProperties.getInt("ro.hwui.max_texture_allocation_size", DefaultSize),
                 DefaultSize);
     }
@@ -262,7 +262,7 @@
     protected void throwIfCannotDraw(Bitmap bitmap) {
         super.throwIfCannotDraw(bitmap);
         int bitmapSize = bitmap.getByteCount();
-        if (bitmapSize > MAX_BITMAP_SIZE) {
+        if (bitmap.getConfig() != Bitmap.Config.HARDWARE && bitmapSize > MAX_BITMAP_SIZE) {
             throw new RuntimeException(
                     "Canvas: trying to draw too large(" + bitmapSize + "bytes) bitmap.");
         }
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index 411a10b..5211e3a 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -165,7 +165,7 @@
     public String toShortString() {
         return toShortString(new StringBuilder(32));
     }
-    
+
     /**
      * Return a string representation of the rectangle in a compact form.
      * @hide
@@ -184,7 +184,7 @@
      *
      * <p>You can later recover the Rect from this string through
      * {@link #unflattenFromString(String)}.
-     * 
+     *
      * @return Returns a new String of the form "left top right bottom"
      */
     @NonNull
@@ -314,7 +314,7 @@
     public final int height() {
         return bottom - top;
     }
-    
+
     /**
      * @return the horizontal center of the rectangle. If the computed value
      *         is fractional, this method returns the largest integer that is
@@ -323,7 +323,7 @@
     public final int centerX() {
         return (left + right) >> 1;
     }
-    
+
     /**
      * @return the vertical center of the rectangle. If the computed value
      *         is fractional, this method returns the largest integer that is
@@ -332,14 +332,14 @@
     public final int centerY() {
         return (top + bottom) >> 1;
     }
-    
+
     /**
      * @return the exact horizontal center of the rectangle as a float.
      */
     public final float exactCenterX() {
         return (left + right) * 0.5f;
     }
-    
+
     /**
      * @return the exact vertical center of the rectangle as a float.
      */
@@ -493,7 +493,7 @@
      * @param top The top of the rectangle being tested for containment
      * @param right The right side of the rectangle being tested for containment
      * @param bottom The bottom of the rectangle being tested for containment
-     * @return true iff the the 4 specified sides of a rectangle are inside or
+     * @return true iff the 4 specified sides of a rectangle are inside or
      *              equal to this rectangle
      */
     public boolean contains(int left, int top, int right, int bottom) {
@@ -548,7 +548,7 @@
         }
         return false;
     }
-    
+
     /**
      * If the specified rectangle intersects this rectangle, return true and set
      * this rectangle to that intersection, otherwise return false and do not
@@ -670,7 +670,7 @@
     public void union(@NonNull Rect r) {
         union(r.left, r.top, r.right, r.bottom);
     }
-    
+
     /**
      * Update this Rect to enclose itself and the [x,y] coordinate. There is no
      * check to see that this rectangle is non-empty.
diff --git a/graphics/java/android/graphics/RectF.java b/graphics/java/android/graphics/RectF.java
index ff50a0c..5b9b764 100644
--- a/graphics/java/android/graphics/RectF.java
+++ b/graphics/java/android/graphics/RectF.java
@@ -38,7 +38,7 @@
     public float top;
     public float right;
     public float bottom;
-    
+
     /**
      * Create a new empty RectF. All coordinates are initialized to 0.
      */
@@ -78,7 +78,7 @@
             bottom = r.bottom;
         }
     }
-    
+
     public RectF(@Nullable Rect r) {
         if (r == null) {
             left = top = right = bottom = 0.0f;
@@ -121,7 +121,7 @@
     public String toShortString() {
         return toShortString(new StringBuilder(32));
     }
-    
+
     /**
      * Return a string representation of the rectangle in a compact form.
      * @hide
@@ -134,7 +134,7 @@
         sb.append(','); sb.append(bottom); sb.append(']');
         return sb.toString();
     }
-    
+
     /**
      * Print short representation to given writer.
      * @hide
@@ -183,14 +183,14 @@
     public final float centerY() {
         return (top + bottom) * 0.5f;
     }
-    
+
     /**
      * Set the rectangle to (0,0,0,0)
      */
     public void setEmpty() {
         left = right = top = bottom = 0;
     }
-    
+
     /**
      * Set the rectangle's coordinates to the specified values. Note: no range
      * checking is performed, so it is up to the caller to ensure that
@@ -220,7 +220,7 @@
         this.right  = src.right;
         this.bottom = src.bottom;
     }
-    
+
     /**
      * Copy the coordinates from src into this rectangle.
      *
@@ -261,7 +261,7 @@
         left = newLeft;
         top = newTop;
     }
-    
+
     /**
      * Inset the rectangle by (dx,dy). If dx is positive, then the sides are
      * moved inwards, making the rectangle narrower. If dx is negative, then the
@@ -293,7 +293,7 @@
         return left < right && top < bottom  // check for empty first
                 && x >= left && x < right && y >= top && y < bottom;
     }
-    
+
     /**
      * Returns true iff the 4 specified sides of a rectangle are inside or equal
      * to this rectangle. i.e. is this rectangle a superset of the specified
@@ -303,7 +303,7 @@
      * @param top The top of the rectangle being tested for containment
      * @param right The right side of the rectangle being tested for containment
      * @param bottom The bottom of the rectangle being tested for containment
-     * @return true iff the the 4 specified sides of a rectangle are inside or
+     * @return true iff the 4 specified sides of a rectangle are inside or
      *              equal to this rectangle
      */
     public boolean contains(float left, float top, float right, float bottom) {
@@ -313,7 +313,7 @@
                 && this.left <= left && this.top <= top
                 && this.right >= right && this.bottom >= bottom;
     }
-    
+
     /**
      * Returns true iff the specified rectangle r is inside or equal to this
      * rectangle. An empty rectangle never contains another rectangle.
@@ -329,7 +329,7 @@
                 && left <= r.left && top <= r.top
                 && right >= r.right && bottom >= r.bottom;
     }
-    
+
     /**
      * If the rectangle specified by left,top,right,bottom intersects this
      * rectangle, return true and set this rectangle to that intersection,
@@ -367,7 +367,7 @@
         }
         return false;
     }
-    
+
     /**
      * If the specified rectangle intersects this rectangle, return true and set
      * this rectangle to that intersection, otherwise return false and do not
@@ -382,7 +382,7 @@
     public boolean intersect(@NonNull RectF r) {
         return intersect(r.left, r.top, r.right, r.bottom);
     }
-    
+
     /**
      * If rectangles a and b intersect, return true and set this rectangle to
      * that intersection, otherwise return false and do not change this
@@ -406,7 +406,7 @@
         }
         return false;
     }
-    
+
     /**
      * Returns true if this rectangle intersects the specified rectangle.
      * In no event is this rectangle modified. No check is performed to see
@@ -426,7 +426,7 @@
         return this.left < right && left < this.right
                 && this.top < bottom && top < this.bottom;
     }
-    
+
     /**
      * Returns true iff the two specified rectangles intersect. In no event are
      * either of the rectangles modified. To record the intersection,
@@ -441,7 +441,7 @@
         return a.left < b.right && b.left < a.right
                 && a.top < b.bottom && b.top < a.bottom;
     }
-    
+
     /**
      * Set the dst integer Rect by rounding this rectangle's coordinates
      * to their nearest integer values.
@@ -489,7 +489,7 @@
             }
         }
     }
-    
+
     /**
      * Update this Rect to enclose itself and the specified rectangle. If the
      * specified rectangle is empty, nothing is done. If this rectangle is empty
@@ -500,7 +500,7 @@
     public void union(@NonNull RectF r) {
         union(r.left, r.top, r.right, r.bottom);
     }
-    
+
     /**
      * Update this Rect to enclose itself and the [x,y] coordinate. There is no
      * check to see that this rectangle is non-empty.
@@ -520,7 +520,7 @@
             bottom = y;
         }
     }
-    
+
     /**
      * Swap top/bottom or left/right if there are flipped (i.e. left > right
      * and/or top > bottom). This can be called if
@@ -548,7 +548,7 @@
     public int describeContents() {
         return 0;
     }
-    
+
     /**
      * Write this rectangle to the specified parcel. To restore a rectangle from
      * a parcel, use readFromParcel()
@@ -561,7 +561,7 @@
         out.writeFloat(right);
         out.writeFloat(bottom);
     }
-    
+
     public static final @android.annotation.NonNull Parcelable.Creator<RectF> CREATOR = new Parcelable.Creator<RectF>() {
         /**
          * Return a new rectangle from the data in the specified parcel.
@@ -572,7 +572,7 @@
             r.readFromParcel(in);
             return r;
         }
-        
+
         /**
          * Return an array of rectangles of the specified size.
          */
@@ -581,7 +581,7 @@
             return new RectF[size];
         }
     };
-    
+
     /**
      * Set the rectangle's coordinates from the data stored in the specified
      * parcel. To write a rectangle to a parcel, call writeToParcel().
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 2732569..0650b78 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -765,12 +765,12 @@
      * Default value is false. See
      * {@link #setProjectBackwards(boolean)} for a description of what this entails.
      *
-     * @param shouldRecieve True if this RenderNode is a projection receiver, false otherwise.
+     * @param shouldReceive True if this RenderNode is a projection receiver, false otherwise.
      *                      Default is false.
      * @return True if the value changed, false if the new value was the same as the previous value.
      */
-    public boolean setProjectionReceiver(boolean shouldRecieve) {
-        return nSetProjectionReceiver(mNativeRenderNode, shouldRecieve);
+    public boolean setProjectionReceiver(boolean shouldReceive) {
+        return nSetProjectionReceiver(mNativeRenderNode, shouldReceive);
     }
 
     /**
@@ -1799,7 +1799,7 @@
     private static native boolean nSetProjectBackwards(long renderNode, boolean shouldProject);
 
     @CriticalNative
-    private static native boolean nSetProjectionReceiver(long renderNode, boolean shouldRecieve);
+    private static native boolean nSetProjectionReceiver(long renderNode, boolean shouldReceive);
 
     @CriticalNative
     private static native boolean nSetOutlineRoundRect(long renderNode, int left, int top,
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index 50b167e..3256f31 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -318,7 +318,7 @@
     }
 
     /**
-     * Releases the the texture content. This is needed in single buffered mode to allow the image
+     * Releases the texture content. This is needed in single buffered mode to allow the image
      * content producer to take ownership of the image buffer.
      * <p>
      * For more information see {@link #SurfaceTexture(int, boolean)}.
@@ -431,7 +431,7 @@
      * error.
      * <p>
      * Note that while calling this method causes all the buffers to be freed
-     * from the perspective of the the SurfaceTexture, if there are additional
+     * from the perspective of the SurfaceTexture, if there are additional
      * references on the buffers (e.g. if a buffer is referenced by a client or
      * by OpenGL ES as a texture) then those buffer will remain allocated.
      * <p>
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 4c4e8fa..fd78816 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -600,7 +600,7 @@
          * {@link #setWeight} and {@link #setItalic}.
          *
          * If {@link #setWeight} is not called, the fallback family keeps the default weight.
-         * Similary, if {@link #setItalic} is not called, the fallback family keeps the default
+         * Similarly, if {@link #setItalic} is not called, the fallback family keeps the default
          * italic information. For example, calling {@code builder.setFallback("sans-serif-light")}
          * is equivalent to calling {@code builder.setFallback("sans-serif").setWeight(300)} in
          * terms of fallback. The default weight and italic information are overridden by calling
@@ -794,7 +794,7 @@
         /**
          * Returns the maximum capacity of custom fallback families.
          *
-         * This includes the the first font family passed to the constructor.
+         * This includes the first font family passed to the constructor.
          * It is guaranteed that the value will be greater than or equal to 64.
          *
          * @return the maximum number of font families for the custom fallback
@@ -816,7 +816,7 @@
         /**
          * Sets a system fallback by name.
          *
-         * You can specify generic font familiy names or OEM specific family names. If the system
+         * You can specify generic font family names or OEM specific family names. If the system
          * don't have a specified fallback, the default fallback is used instead.
          * For more information about generic font families, see <a
          * href="https://www.w3.org/TR/css-fonts-4/#generic-font-families">CSS specification</a>
diff --git a/graphics/java/android/graphics/Xfermode.java b/graphics/java/android/graphics/Xfermode.java
index 81769e2..6bb22a1 100644
--- a/graphics/java/android/graphics/Xfermode.java
+++ b/graphics/java/android/graphics/Xfermode.java
@@ -16,8 +16,8 @@
 
 // This file was generated from the C++ include file: SkXfermode.h
 // Any changes made to this file will be discarded by the build.
-// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, 
-// or one of the auxilary file specifications in device/tools/gluemaker.
+// To change this file, either edit the include, or device/tools/gluemaker/main.cpp,
+// or one of the auxiliary file specifications in device/tools/gluemaker.
 
 package android.graphics;
 
@@ -28,7 +28,7 @@
  * Xfermode is the base class for objects that are called to implement custom
  * "transfer-modes" in the drawing pipeline. The static function Create(Modes)
  * can be called to return an instance of any of the predefined subclasses as
- * specified in the Modes enum. When an Xfermode is assigned to an Paint, then
+ * specified in the Modes enum. When an Xfermode is assigned to a Paint, then
  * objects drawn with that paint have the xfermode applied.
  */
 public class Xfermode {
diff --git a/graphics/java/android/graphics/YuvImage.java b/graphics/java/android/graphics/YuvImage.java
index ce35b55..b0c7f20 100644
--- a/graphics/java/android/graphics/YuvImage.java
+++ b/graphics/java/android/graphics/YuvImage.java
@@ -63,7 +63,7 @@
     private int mWidth;
 
     /**
-     * The height of the the image.
+     * The height of the image.
      */
     private int mHeight;
 
diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
index 688425a..7ee7d6b 100644
--- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
+++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
@@ -98,7 +98,7 @@
      * extra content to reveal within the clip path when performing affine transformations on the
      * layers.
      *
-     * Each layers will reserve 25% of it's width and height.
+     * Each layers will reserve 25% of its width and height.
      *
      * As a result, the view port of the layers is smaller than their intrinsic width and height.
      */
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 4972e92..7f2feac 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -839,7 +839,7 @@
     }
 
     /**
-     * Describes the current state, as a union of primitve states, such as
+     * Describes the current state, as a union of primitive states, such as
      * {@link android.R.attr#state_focused},
      * {@link android.R.attr#state_selected}, etc.
      * Some drawables may modify their imagery based on the selected state.
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 166a795..29d033e 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -936,7 +936,7 @@
     }
 
     /**
-     * Retrn the inner radius of the ring
+     * Return the inner radius of the ring
      *
      * @see #setInnerRadius(int)
      * @attr ref android.R.styleable#GradientDrawable_innerRadius
diff --git a/graphics/java/android/graphics/drawable/shapes/PathShape.java b/graphics/java/android/graphics/drawable/shapes/PathShape.java
index 393fdee..299f6d5 100644
--- a/graphics/java/android/graphics/drawable/shapes/PathShape.java
+++ b/graphics/java/android/graphics/drawable/shapes/PathShape.java
@@ -93,7 +93,7 @@
             && Float.compare(pathShape.mStdHeight, mStdHeight) == 0
             && Float.compare(pathShape.mScaleX, mScaleX) == 0
             && Float.compare(pathShape.mScaleY, mScaleY) == 0
-            // Path does not have equals implementation but incase it gains one, use it here
+            // Path does not have equals implementation but in case it gains one, use it here
             && Objects.equals(mPath, pathShape.mPath);
     }
 
diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java
index 318aadd..2893177 100644
--- a/graphics/java/android/graphics/fonts/Font.java
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -789,7 +789,7 @@
             return false;
         }
 
-        // ByteBuffer#equals compares all bytes which is not performant for e.g HashMap. Since
+        // ByteBuffer#equals compares all bytes which is not performant for e.g. HashMap. Since
         // underlying native font object holds buffer address, check if this buffer points exactly
         // the same address as a shortcut of equality. For being compatible with of API30 or before,
         // check buffer position even if the buffer points the same address.
diff --git a/graphics/java/android/graphics/fonts/FontFileUtil.java b/graphics/java/android/graphics/fonts/FontFileUtil.java
index ff38282..abcafb6 100644
--- a/graphics/java/android/graphics/fonts/FontFileUtil.java
+++ b/graphics/java/android/graphics/fonts/FontFileUtil.java
@@ -34,7 +34,7 @@
  */
 public class FontFileUtil {
 
-    private FontFileUtil() {}  // Do not instanciate
+    private FontFileUtil() {}  // Do not instantiate
 
     /**
      * Unpack the weight value from packed integer.
@@ -87,7 +87,7 @@
         }
 
         if (weight != -1 && italic != -1) {
-            // Both weight/italic style are specifeid by variation settings.
+            // Both weight/italic style are specified by variation settings.
             // No need to look into OS/2 table.
             // TODO: Good to look HVAR table to check if this font supports wght/ital axes.
             return pack(weight, italic == 1);
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index a90961e..f727f5b 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -216,7 +216,7 @@
                 } else if (defaultFamily != null) {
                     familyListSet.familyList.add(defaultFamily);
                 } else {
-                    // There is no valid for for default fallback. Ignore.
+                    // There is no valid for default fallback. Ignore.
                 }
             }
         }
diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java
index 0c6d4bd..d8cf21e 100644
--- a/graphics/java/android/graphics/text/LineBreaker.java
+++ b/graphics/java/android/graphics/text/LineBreaker.java
@@ -376,8 +376,8 @@
      * @see LineBreaker#computeLineBreaks
      */
     public static class Result {
-        // Following two contstant must be synced with minikin's line breaker.
-        // TODO(nona): Remove these constatns by introducing native methods.
+        // Following two constants must be synced with minikin's line breaker.
+        // TODO(nona): Remove these constants by introducing native methods.
         private static final int TAB_MASK = 0x20000000;
         private static final int HYPHEN_MASK = 0xFF;
         private static final int START_HYPHEN_MASK = 0x18;  // 0b11000
diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java
index f8328b1..671eb6e 100644
--- a/graphics/java/android/graphics/text/PositionedGlyphs.java
+++ b/graphics/java/android/graphics/text/PositionedGlyphs.java
@@ -139,7 +139,7 @@
      * Returns the glyph ID used for drawing the glyph at the given index.
      *
      * @param index the glyph index
-     * @return An glyph ID of the font.
+     * @return A glyph ID of the font.
      */
     @IntRange(from = 0)
     public int getGlyphId(@IntRange(from = 0) int index) {
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
deleted file mode 100644
index d1d7c14..0000000
--- a/keystore/java/android/security/KeyStore.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2009 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;
-
-/**
- * This class provides some constants and helper methods related to Android's Keystore service.
- * This class was originally much larger, but its functionality was superseded by other classes.
- * It now just contains a few remaining pieces for which the users haven't been updated yet.
- * You may be looking for {@link java.security.KeyStore} instead.
- *
- * @hide
- */
-public class KeyStore {
-
-    // Used for UID field to indicate the calling UID.
-    public static final int UID_SELF = -1;
-}
diff --git a/ktfmt_includes.txt b/ktfmt_includes.txt
index fe47503..0ac6265 100644
--- a/ktfmt_includes.txt
+++ b/ktfmt_includes.txt
@@ -5,8 +5,6 @@
 -packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
 -packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
 -packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
 -packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
 -packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt
 -packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java
index d923a46..d241641 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java
@@ -16,6 +16,8 @@
 
 package androidx.window.common;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.Application;
 import android.os.Bundle;
@@ -26,30 +28,30 @@
  */
 public class EmptyLifecycleCallbacksAdapter implements Application.ActivityLifecycleCallbacks {
     @Override
-    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+    public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
     }
 
     @Override
-    public void onActivityStarted(Activity activity) {
+    public void onActivityStarted(@NonNull Activity activity) {
     }
 
     @Override
-    public void onActivityResumed(Activity activity) {
+    public void onActivityResumed(@NonNull Activity activity) {
     }
 
     @Override
-    public void onActivityPaused(Activity activity) {
+    public void onActivityPaused(@NonNull Activity activity) {
     }
 
     @Override
-    public void onActivityStopped(Activity activity) {
+    public void onActivityStopped(@NonNull Activity activity) {
     }
 
     @Override
-    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+    public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
     }
 
     @Override
-    public void onActivityDestroyed(Activity activity) {
+    public void onActivityDestroyed(@NonNull Activity activity) {
     }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index a0d6fce..29936cc 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -33,8 +33,11 @@
 import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_RIGHT;
 import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_TOP;
 
-import android.annotation.DimenRes;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.annotation.Nullable;
+import android.app.Activity;
 import android.app.ActivityThread;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -42,6 +45,7 @@
 import android.graphics.Color;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.RotateDrawable;
 import android.hardware.display.DisplayManager;
@@ -51,9 +55,11 @@
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
+import android.view.VelocityTracker;
 import android.view.View;
 import android.view.WindowManager;
 import android.view.WindowlessWindowManager;
+import android.view.animation.PathInterpolator;
 import android.widget.FrameLayout;
 import android.widget.ImageButton;
 import android.window.InputTransferToken;
@@ -95,6 +101,16 @@
     @VisibleForTesting
     static final int DEFAULT_DIVIDER_WIDTH_DP = 24;
 
+    @VisibleForTesting
+    static final PathInterpolator FLING_ANIMATION_INTERPOLATOR =
+            new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+    @VisibleForTesting
+    static final int FLING_ANIMATION_DURATION = 250;
+    @VisibleForTesting
+    static final int MIN_DISMISS_VELOCITY_DP_PER_SECOND = 600;
+    @VisibleForTesting
+    static final int MIN_FLING_VELOCITY_DP_PER_SECOND = 400;
+
     private final int mTaskId;
 
     @NonNull
@@ -107,6 +123,14 @@
     private final Executor mCallbackExecutor;
 
     /**
+     * The VelocityTracker of the divider, used to track the dragging velocity. This field is
+     * {@code null} until dragging starts.
+     */
+    @GuardedBy("mLock")
+    @Nullable
+    VelocityTracker mVelocityTracker;
+
+    /**
      * The {@link Properties} of the divider. This field is {@code null} when no divider should be
      * drawn, e.g. when the split doesn't have {@link DividerAttributes} or when the decor surface
      * is not available.
@@ -213,7 +237,11 @@
                             isVerticalSplit,
                             isReversedLayout,
                             parentInfo.getDisplayId(),
-                            isDraggableExpandType
+                            isDraggableExpandType,
+                            getContainerBackgroundColor(topSplitContainer.getPrimaryContainer(),
+                                    DEFAULT_PRIMARY_VEIL_COLOR),
+                            getContainerBackgroundColor(topSplitContainer.getSecondaryContainer(),
+                                    DEFAULT_SECONDARY_VEIL_COLOR)
                     ));
         }
     }
@@ -242,6 +270,29 @@
     }
 
     /**
+     * Returns the window background color of the top activity in the container if set, or the
+     * default color if the background color of the top activity is unavailable.
+     */
+    @VisibleForTesting
+    @NonNull
+    static Color getContainerBackgroundColor(
+            @NonNull TaskFragmentContainer container, @NonNull Color defaultColor) {
+        final Activity activity = container.getTopNonFinishingActivity();
+        if (activity == null) {
+            // This can happen when the activities in the container are from a different process.
+            // TODO(b/340984203) Report whether the top activity is in the same process. Use default
+            // color if not.
+            return defaultColor;
+        }
+
+        final Drawable drawable = activity.getWindow().getDecorView().getBackground();
+        if (drawable instanceof ColorDrawable colorDrawable) {
+            return Color.valueOf(colorDrawable.getColor());
+        }
+        return defaultColor;
+    }
+
+    /**
      * Creates a decor surface for the TaskFragment if no decor surface exists, or changes the owner
      * of the existing decor surface to be the specified TaskFragment.
      *
@@ -341,13 +392,11 @@
                 applicationContext.getResources().getDisplayMetrics());
     }
 
-    private static int getDimensionDp(@DimenRes int resId) {
-        final Context context = ActivityThread.currentActivityThread().getApplication();
-        final int px = context.getResources().getDimensionPixelSize(resId);
-        return (int) TypedValue.convertPixelsToDimension(
-                COMPLEX_UNIT_DIP,
-                px,
-                context.getResources().getDisplayMetrics());
+    private static float getDisplayDensity() {
+        // TODO(b/329193115) support divider on secondary display
+        final Context applicationContext =
+                ActivityThread.currentActivityThread().getApplication();
+        return applicationContext.getResources().getDisplayMetrics().density;
     }
 
     /**
@@ -439,10 +488,6 @@
         }
 
         if (dividerAttributes.getDividerType() == DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
-            // Draggable divider width must be larger than the drag handle size.
-            widthDp = Math.max(widthDp,
-                    getDimensionDp(R.dimen.activity_embedding_divider_touch_target_width));
-
             // Update minRatio and maxRatio only when it is a draggable divider.
             if (minRatio == RATIO_SYSTEM_DEFAULT) {
                 minRatio = DEFAULT_MIN_RATIO;
@@ -462,24 +507,27 @@
     @Override
     public boolean onTouch(@NonNull View view, @NonNull MotionEvent event) {
         synchronized (mLock) {
-            final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
-            mDividerPosition = calculateDividerPosition(
-                    event, taskBounds, mRenderer.mDividerWidthPx, mProperties.mDividerAttributes,
-                    mProperties.mIsVerticalSplit, calculateMinPosition(), calculateMaxPosition());
-            mRenderer.setDividerPosition(mDividerPosition);
-            switch (event.getAction()) {
-                case MotionEvent.ACTION_DOWN:
-                    onStartDragging();
-                    break;
-                case MotionEvent.ACTION_UP:
-                case MotionEvent.ACTION_CANCEL:
-                    onFinishDragging();
-                    break;
-                case MotionEvent.ACTION_MOVE:
-                    onDrag();
-                    break;
-                default:
-                    break;
+            if (mProperties != null && mRenderer != null) {
+                final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
+                mDividerPosition = calculateDividerPosition(
+                        event, taskBounds, mRenderer.mDividerWidthPx,
+                        mProperties.mDividerAttributes, mProperties.mIsVerticalSplit,
+                        calculateMinPosition(), calculateMaxPosition());
+                mRenderer.setDividerPosition(mDividerPosition);
+                switch (event.getAction()) {
+                    case MotionEvent.ACTION_DOWN:
+                        onStartDragging(event);
+                        break;
+                    case MotionEvent.ACTION_UP:
+                    case MotionEvent.ACTION_CANCEL:
+                        onFinishDragging(event);
+                        break;
+                    case MotionEvent.ACTION_MOVE:
+                        onDrag(event);
+                        break;
+                    default:
+                        break;
+                }
             }
         }
 
@@ -489,11 +537,17 @@
     }
 
     @GuardedBy("mLock")
-    private void onStartDragging() {
+    private void onStartDragging(@NonNull MotionEvent event) {
+        mVelocityTracker = VelocityTracker.obtain();
+        mVelocityTracker.addMovement(event);
+
         mRenderer.mIsDragging = true;
         mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging);
+        mRenderer.updateSurface();
+
+        // Veil visibility change should be applied together with the surface boost transaction in
+        // the wct.
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-        mRenderer.updateSurface(t);
         mRenderer.showVeils(t);
 
         // Callbacks must be executed on the executor to release mLock and prevent deadlocks.
@@ -508,19 +562,84 @@
     }
 
     @GuardedBy("mLock")
-    private void onDrag() {
-        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-        mRenderer.updateSurface(t);
-        t.apply();
+    private void onDrag(@NonNull MotionEvent event) {
+        if (mVelocityTracker != null) {
+            mVelocityTracker.addMovement(event);
+        }
+        mRenderer.updateSurface();
     }
 
     @GuardedBy("mLock")
-    private void onFinishDragging() {
-        mDividerPosition = adjustDividerPositionForSnapPoints(mDividerPosition);
-        mRenderer.setDividerPosition(mDividerPosition);
+    private void onFinishDragging(@NonNull MotionEvent event) {
+        float velocity = 0.0f;
+        if (mVelocityTracker != null) {
+            mVelocityTracker.addMovement(event);
+            mVelocityTracker.computeCurrentVelocity(1000 /* units */);
+            velocity = mProperties.mIsVerticalSplit
+                    ? mVelocityTracker.getXVelocity()
+                    : mVelocityTracker.getYVelocity();
+            mVelocityTracker.recycle();
+        }
 
+        final int prevDividerPosition = mDividerPosition;
+        mDividerPosition = dividerPositionForSnapPoints(mDividerPosition, velocity);
+        if (mDividerPosition != prevDividerPosition) {
+            ValueAnimator animator = getFlingAnimator(prevDividerPosition, mDividerPosition);
+            animator.start();
+        } else {
+            onDraggingEnd();
+        }
+    }
+
+    @GuardedBy("mLock")
+    @NonNull
+    @VisibleForTesting
+    ValueAnimator getFlingAnimator(int prevDividerPosition, int snappedDividerPosition) {
+        final ValueAnimator animator =
+                getValueAnimator(prevDividerPosition, snappedDividerPosition);
+        animator.addUpdateListener(animation -> {
+            synchronized (mLock) {
+                updateDividerPosition((int) animation.getAnimatedValue());
+            }
+        });
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                synchronized (mLock) {
+                    onDraggingEnd();
+                }
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                synchronized (mLock) {
+                    onDraggingEnd();
+                }
+            }
+        });
+        return animator;
+    }
+
+    @VisibleForTesting
+    static ValueAnimator getValueAnimator(int prevDividerPosition, int snappedDividerPosition) {
+        ValueAnimator animator = ValueAnimator
+                .ofInt(prevDividerPosition, snappedDividerPosition)
+                .setDuration(FLING_ANIMATION_DURATION);
+        animator.setInterpolator(FLING_ANIMATION_INTERPOLATOR);
+        return animator;
+    }
+
+    @GuardedBy("mLock")
+    private void updateDividerPosition(int position) {
+        mRenderer.setDividerPosition(position);
+        mRenderer.updateSurface();
+    }
+
+    @GuardedBy("mLock")
+    private void onDraggingEnd() {
+        // Veil visibility change should be applied together with the surface boost transaction in
+        // the wct.
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-        mRenderer.updateSurface(t);
         mRenderer.hideVeils(t);
 
         // Callbacks must be executed on the executor to release mLock and prevent deadlocks.
@@ -542,38 +661,78 @@
 
     /**
      * Returns the divider position adjusted for the min max ratio and fullscreen expansion.
-     *
-     * If the dragging position is above the {@link DividerAttributes#getPrimaryMaxRatio()} or below
-     * {@link DividerAttributes#getPrimaryMinRatio()} and
-     * {@link DividerAttributes#isDraggingToFullscreenAllowed} is {@code true}, the system will
-     * choose a snap algorithm to adjust the ending position to either fully expand one container or
-     * move the divider back to the specified min/max ratio.
-     *
-     * TODO(b/327067596) implement snap algorithm
-     *
      * The adjusted divider position is in the range of [minPosition, maxPosition] for a split, 0
      * for expanded right (bottom) container, or task width (height) minus the divider width for
      * expanded left (top) container.
      */
     @GuardedBy("mLock")
-    private int adjustDividerPositionForSnapPoints(int dividerPosition) {
+    private int dividerPositionForSnapPoints(int dividerPosition, float velocity) {
         final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
         final int minPosition = calculateMinPosition();
         final int maxPosition = calculateMaxPosition();
         final int fullyExpandedPosition = mProperties.mIsVerticalSplit
                 ? taskBounds.right - mRenderer.mDividerWidthPx
                 : taskBounds.bottom - mRenderer.mDividerWidthPx;
+
         if (isDraggingToFullscreenAllowed(mProperties.mDividerAttributes)) {
-            if (dividerPosition < minPosition) {
-                return 0;
-            }
-            if (dividerPosition > maxPosition) {
-                return fullyExpandedPosition;
-            }
+            final float displayDensity = getDisplayDensity();
+            return dividerPositionWithDraggingToFullscreenAllowed(
+                    dividerPosition,
+                    minPosition,
+                    maxPosition,
+                    fullyExpandedPosition,
+                    velocity,
+                    displayDensity);
         }
         return Math.clamp(dividerPosition, minPosition, maxPosition);
     }
 
+    /**
+     * Returns the divider position given a set of position options. A snap algorithm is used to
+     * adjust the ending position to either fully expand one container or move the divider back to
+     * the specified min/max ratio depending on the dragging velocity.
+     */
+    @VisibleForTesting
+    static int dividerPositionWithDraggingToFullscreenAllowed(int dividerPosition, int minPosition,
+            int maxPosition, int fullyExpandedPosition, float velocity, float displayDensity) {
+        final float minDismissVelocityPxPerSecond =
+                MIN_DISMISS_VELOCITY_DP_PER_SECOND * displayDensity;
+        final float minFlingVelocityPxPerSecond =
+                MIN_FLING_VELOCITY_DP_PER_SECOND * displayDensity;
+        if (dividerPosition < minPosition && velocity < -minDismissVelocityPxPerSecond) {
+            return 0;
+        }
+        if (dividerPosition > maxPosition && velocity > minDismissVelocityPxPerSecond) {
+            return fullyExpandedPosition;
+        }
+        if (Math.abs(velocity) < minFlingVelocityPxPerSecond) {
+            if (dividerPosition >= minPosition && dividerPosition <= maxPosition) {
+                return dividerPosition;
+            }
+            int[] possiblePositions = {0, minPosition, maxPosition, fullyExpandedPosition};
+            return snap(dividerPosition, possiblePositions);
+        }
+        if (velocity < 0) {
+            return 0;
+        } else {
+            return fullyExpandedPosition;
+        }
+    }
+
+    /** Calculates the snapped divider position based on the possible positions and distance. */
+    private static int snap(int dividerPosition, int[] possiblePositions) {
+        int snappedPosition = dividerPosition;
+        float minDistance = Float.MAX_VALUE;
+        for (int position : possiblePositions) {
+            float distance = Math.abs(dividerPosition - position);
+            if (distance < minDistance) {
+                snappedPosition = position;
+                minDistance = distance;
+            }
+        }
+        return snappedPosition;
+    }
+
     private static void setDecorSurfaceBoosted(
             @NonNull WindowContainerTransaction wct,
             @Nullable IBinder decorSurfaceOwner,
@@ -800,6 +959,8 @@
         private final int mDisplayId;
         private final boolean mIsReversedLayout;
         private final boolean mIsDraggableExpandType;
+        private final Color mPrimaryVeilColor;
+        private final Color mSecondaryVeilColor;
 
         @VisibleForTesting
         Properties(
@@ -810,7 +971,9 @@
                 boolean isVerticalSplit,
                 boolean isReversedLayout,
                 int displayId,
-                boolean isDraggableExpandType) {
+                boolean isDraggableExpandType,
+                @NonNull Color primaryVeilColor,
+                @NonNull Color secondaryVeilColor) {
             mConfiguration = configuration;
             mDividerAttributes = dividerAttributes;
             mDecorSurface = decorSurface;
@@ -819,6 +982,8 @@
             mIsReversedLayout = isReversedLayout;
             mDisplayId = displayId;
             mIsDraggableExpandType = isDraggableExpandType;
+            mPrimaryVeilColor = primaryVeilColor;
+            mSecondaryVeilColor = secondaryVeilColor;
         }
 
         /**
@@ -840,7 +1005,9 @@
                     && a.mIsVerticalSplit == b.mIsVerticalSplit
                     && a.mDisplayId == b.mDisplayId
                     && a.mIsReversedLayout == b.mIsReversedLayout
-                    && a.mIsDraggableExpandType == b.mIsDraggableExpandType;
+                    && a.mIsDraggableExpandType == b.mIsDraggableExpandType
+                    && a.mPrimaryVeilColor.equals(b.mPrimaryVeilColor)
+                    && a.mSecondaryVeilColor.equals(b.mSecondaryVeilColor);
         }
 
         private static boolean areSameSurfaces(
@@ -877,17 +1044,21 @@
         @NonNull
         private final FrameLayout mDividerLayout;
         @NonNull
+        private final View mDividerLine;
+        private View mDragHandle;
+        @NonNull
         private final View.OnTouchListener mListener;
         @NonNull
         private Properties mProperties;
         private int mDividerWidthPx;
+        private int mHandleWidthPx;
         @Nullable
         private SurfaceControl mPrimaryVeil;
         @Nullable
         private SurfaceControl mSecondaryVeil;
         private boolean mIsDragging;
         private int mDividerPosition;
-        private View mDragHandle;
+        private int mDividerSurfaceWidthPx;
 
         private Renderer(@NonNull Properties properties, @NonNull View.OnTouchListener listener) {
             mProperties = properties;
@@ -905,6 +1076,7 @@
                     context, displayManager.getDisplay(mProperties.mDisplayId),
                     mWindowlessWindowManager, "DividerContainer");
             mDividerLayout = new FrameLayout(context);
+            mDividerLine = new View(context);
 
             update();
         }
@@ -921,6 +1093,17 @@
             mDividerWidthPx = getDividerWidthPx(mProperties.mDividerAttributes);
             mDividerPosition = mProperties.mInitialDividerPosition;
             mWindowlessWindowManager.setConfiguration(mProperties.mConfiguration);
+
+            if (mProperties.mDividerAttributes.getDividerType()
+                    == DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
+                // TODO(b/329193115) support divider on secondary display
+                final Context context = ActivityThread.currentActivityThread().getApplication();
+                mHandleWidthPx = context.getResources().getDimensionPixelSize(
+                        R.dimen.activity_embedding_divider_touch_target_width);
+            } else {
+                mHandleWidthPx = 0;
+            }
+
             // TODO handle synchronization between surface transactions and WCT.
             final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
             updateSurface(t);
@@ -947,16 +1130,64 @@
          * Updates the positions and crops of the divider surface and veil surfaces. This method
          * should be called when {@link #mProperties} is changed or while dragging to update the
          * position of the divider surface and the veil surfaces.
+         *
+         * This method applies the changes in a stand-alone surface transaction immediately.
+         */
+        private void updateSurface() {
+            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            updateSurface(t);
+            t.apply();
+        }
+
+        /**
+         * Updates the positions and crops of the divider surface and veil surfaces. This method
+         * should be called when {@link #mProperties} is changed or while dragging to update the
+         * position of the divider surface and the veil surfaces.
+         *
+         * This method applies the changes in the provided surface transaction and can be synced
+         * with other changes.
          */
         private void updateSurface(@NonNull SurfaceControl.Transaction t) {
             final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
-            if (mProperties.mIsVerticalSplit) {
-                t.setPosition(mDividerSurface, mDividerPosition, 0.0f);
-                t.setWindowCrop(mDividerSurface, mDividerWidthPx, taskBounds.height());
+
+            int dividerSurfacePosition;
+            if (mProperties.mDividerAttributes.getDividerType()
+                    == DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
+                // When the divider drag handle width is larger than the divider width, the position
+                // of the divider surface is adjusted so that it is large enough to host both the
+                // divider line and the divider drag handle.
+                mDividerSurfaceWidthPx = Math.max(mDividerWidthPx, mHandleWidthPx);
+                dividerSurfacePosition =
+                        mProperties.mIsReversedLayout
+                                ? mDividerPosition
+                                : mDividerPosition + mDividerWidthPx - mDividerSurfaceWidthPx;
+                dividerSurfacePosition = Math.clamp(dividerSurfacePosition, 0,
+                        mProperties.mIsVerticalSplit ? taskBounds.width() : taskBounds.height());
             } else {
-                t.setPosition(mDividerSurface, 0.0f, mDividerPosition);
-                t.setWindowCrop(mDividerSurface, taskBounds.width(), mDividerWidthPx);
+                mDividerSurfaceWidthPx = mDividerWidthPx;
+                dividerSurfacePosition = mDividerPosition;
             }
+
+            if (mProperties.mIsVerticalSplit) {
+                t.setPosition(mDividerSurface, dividerSurfacePosition, 0.0f);
+                t.setWindowCrop(mDividerSurface, mDividerSurfaceWidthPx, taskBounds.height());
+            } else {
+                t.setPosition(mDividerSurface, 0.0f, dividerSurfacePosition);
+                t.setWindowCrop(mDividerSurface, taskBounds.width(), mDividerSurfaceWidthPx);
+            }
+
+            // Update divider line position in the surface
+            if (!mProperties.mIsReversedLayout) {
+                final int offset = mDividerPosition - dividerSurfacePosition;
+                mDividerLine.setX(mProperties.mIsVerticalSplit ? offset : 0);
+                mDividerLine.setY(mProperties.mIsVerticalSplit ? 0 : offset);
+            } else {
+                // For reversed layout, the divider line is always at the start of the divider
+                // surface.
+                mDividerLine.setX(0);
+                mDividerLine.setY(0);
+            }
+
             if (mIsDragging) {
                 updateVeils(t);
             }
@@ -971,14 +1202,14 @@
             final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
             final WindowManager.LayoutParams lp = mProperties.mIsVerticalSplit
                     ? new WindowManager.LayoutParams(
-                            mDividerWidthPx,
+                            mDividerSurfaceWidthPx,
                             taskBounds.height(),
                             TYPE_APPLICATION_PANEL,
                             FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_SLIPPERY,
                             PixelFormat.TRANSLUCENT)
                     : new WindowManager.LayoutParams(
                             taskBounds.width(),
-                            mDividerWidthPx,
+                            mDividerSurfaceWidthPx,
                             TYPE_APPLICATION_PANEL,
                             FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_SLIPPERY,
                             PixelFormat.TRANSLUCENT);
@@ -995,12 +1226,19 @@
          */
         private void updateDivider(@NonNull SurfaceControl.Transaction t) {
             mDividerLayout.removeAllViews();
-            if (mProperties.mIsDraggableExpandType) {
+            mDividerLayout.addView(mDividerLine);
+            if (mProperties.mIsDraggableExpandType && !mIsDragging) {
                 // If a container is fully expanded, the divider overlays on the expanded container.
-                mDividerLayout.setBackgroundColor(Color.TRANSPARENT);
+                mDividerLine.setBackgroundColor(Color.TRANSPARENT);
             } else {
-                mDividerLayout.setBackgroundColor(mProperties.mDividerAttributes.getDividerColor());
+                mDividerLine.setBackgroundColor(mProperties.mDividerAttributes.getDividerColor());
             }
+            final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
+            mDividerLine.setLayoutParams(
+                    mProperties.mIsVerticalSplit
+                            ? new FrameLayout.LayoutParams(mDividerWidthPx, taskBounds.height())
+                            : new FrameLayout.LayoutParams(taskBounds.width(), mDividerWidthPx)
+            );
             if (mProperties.mDividerAttributes.getDividerType()
                     == DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
                 createVeils();
@@ -1027,7 +1265,7 @@
                                     R.dimen.activity_embedding_divider_touch_target_width));
             params.gravity = Gravity.CENTER;
             button.setLayoutParams(params);
-            button.setBackgroundColor(R.color.transparent);
+            button.setBackgroundColor(Color.TRANSPARENT);
 
             final Drawable handle = context.getResources().getDrawable(
                     R.drawable.activity_embedding_divider_handle, context.getTheme());
@@ -1087,8 +1325,8 @@
         }
 
         private void showVeils(@NonNull SurfaceControl.Transaction t) {
-            t.setColor(mPrimaryVeil, colorToFloatArray(DEFAULT_PRIMARY_VEIL_COLOR))
-                    .setColor(mSecondaryVeil, colorToFloatArray(DEFAULT_SECONDARY_VEIL_COLOR))
+            t.setColor(mPrimaryVeil, colorToFloatArray(mProperties.mPrimaryVeilColor))
+                    .setColor(mSecondaryVeil, colorToFloatArray(mProperties.mSecondaryVeilColor))
                     .setLayer(mDividerSurface, DIVIDER_LAYER)
                     .setLayer(mPrimaryVeil, VEIL_LAYER)
                     .setLayer(mSecondaryVeil, VEIL_LAYER)
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index a23a4741..f9a6caf 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -21,6 +21,7 @@
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_PINNED;
 
 import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
 import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
@@ -358,6 +359,13 @@
         wct.addTaskFragmentOperation(fragmentToken, operation);
     }
 
+    void setTaskFragmentPinned(@NonNull WindowContainerTransaction wct,
+            @NonNull IBinder fragmentToken, boolean pinned) {
+        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+                OP_TYPE_SET_PINNED).setBooleanValue(pinned).build();
+        wct.addTaskFragmentOperation(fragmentToken, operation);
+    }
+
     void setTaskFragmentDimOnTask(@NonNull WindowContainerTransaction wct,
             @NonNull IBinder fragmentToken, boolean dimOnTask) {
         final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 59092d4..b764b6e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -28,6 +28,7 @@
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO;
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
 import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CLOSE;
+import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_DRAG_RESIZE;
 import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN;
 import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
@@ -49,6 +50,7 @@
 import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
 import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds;
 import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
+import static androidx.window.extensions.embedding.TaskFragmentContainer.OverlayContainerRestoreParams;
 
 import android.annotation.CallbackExecutor;
 import android.app.Activity;
@@ -132,6 +134,13 @@
     private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
 
     /**
+     * Stores the token of the associated Activity that maps to the
+     * {@link OverlayContainerRestoreParams} of the most recent created overlay container.
+     */
+    @GuardedBy("mLock")
+    final ArrayMap<IBinder, OverlayContainerRestoreParams> mOverlayRestoreParams = new ArrayMap<>();
+
+    /**
      * A developer-defined {@link SplitAttributes} calculator to compute the current
      * {@link SplitAttributes} with the current device and window states.
      * It is registered via {@link #setSplitAttributesCalculator(Function)}
@@ -349,8 +358,7 @@
             // Resets the isolated navigation and updates the container.
             final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
             final WindowContainerTransaction wct = transactionRecord.getTransaction();
-            mPresenter.setTaskFragmentIsolatedNavigation(wct, containerToUnpin,
-                    false /* isolated */);
+            mPresenter.setTaskFragmentPinned(wct, containerToUnpin, false /* pinned */);
             updateContainer(wct, containerToUnpin);
             transactionRecord.apply(false /* shouldApplyIndependently */);
             updateCallbackIfNecessary();
@@ -686,11 +694,20 @@
                                 exception);
                         break;
                     case TYPE_ACTIVITY_REPARENTED_TO_TASK:
+                        final IBinder candidateAssociatedActToken, lastOverlayToken;
+                        if (Flags.fixPipRestoreToOverlay()) {
+                            candidateAssociatedActToken = change.getOtherActivityToken();
+                            lastOverlayToken = change.getTaskFragmentToken();
+                        } else {
+                            candidateAssociatedActToken = lastOverlayToken = null;
+                        }
                         onActivityReparentedToTask(
                                 wct,
                                 taskId,
                                 change.getActivityIntent(),
-                                change.getActivityToken());
+                                change.getActivityToken(),
+                                candidateAssociatedActToken,
+                                lastOverlayToken);
                         break;
                     default:
                         throw new IllegalArgumentException(
@@ -850,6 +867,16 @@
             Log.e(TAG, "onTaskFragmentParentInfoChanged on empty Task id=" + taskId);
             return;
         }
+
+        if (!parentInfo.isVisible()) {
+            // Only making the TaskContainer invisible and drops the other info, and perform the
+            // update when the next time the Task becomes visible.
+            if (taskContainer.isVisible()) {
+                taskContainer.setInvisible();
+            }
+            return;
+        }
+
         // Checks if container should be updated before apply new parentInfo.
         final boolean shouldUpdateContainer = taskContainer.shouldUpdateContainer(parentInfo);
         taskContainer.updateTaskFragmentParentInfo(parentInfo);
@@ -907,11 +934,28 @@
      *                       different process, the server will generate a temporary token that
      *                       the organizer can use to reparent the activity through
      *                       {@link WindowContainerTransaction} if needed.
+     * @param candidateAssociatedActToken The token of the candidate associated-activity.
+     * @param lastOverlayToken The last parent overlay container token.
      */
     @VisibleForTesting
     @GuardedBy("mLock")
     void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
-            int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) {
+            int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken,
+            @Nullable IBinder candidateAssociatedActToken, @Nullable IBinder lastOverlayToken) {
+        // Reparent the activity to an overlay container if needed.
+        final OverlayContainerRestoreParams params = getOverlayContainerRestoreParams(
+                candidateAssociatedActToken, lastOverlayToken);
+        if (params != null) {
+            final Activity associatedActivity = getActivity(candidateAssociatedActToken);
+            final TaskFragmentContainer targetContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                    wct, params.mOptions, params.mIntent, associatedActivity);
+            if (targetContainer != null) {
+                wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(),
+                        activityToken);
+                return;
+            }
+        }
+
         // If the activity belongs to the current app process, we treat it as a new activity
         // launch.
         final Activity activity = getActivity(activityToken);
@@ -956,6 +1000,43 @@
     }
 
     /**
+     * Returns the {@link OverlayContainerRestoreParams} that stored last time the {@code
+     * associatedActivityToken} associated with and only if data matches the {@code overlayToken}.
+     * Otherwise, return {@code null}.
+     */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    @Nullable
+    OverlayContainerRestoreParams getOverlayContainerRestoreParams(
+            @Nullable IBinder associatedActivityToken, @Nullable IBinder overlayToken) {
+        if (!Flags.fixPipRestoreToOverlay()) {
+            return null;
+        }
+
+        if (associatedActivityToken == null || overlayToken == null) {
+            return null;
+        }
+
+        final TaskFragmentContainer.OverlayContainerRestoreParams params =
+                mOverlayRestoreParams.get(associatedActivityToken);
+        if (params == null) {
+            return null;
+        }
+
+        if (params.mOverlayToken != overlayToken) {
+            // Not the same overlay container, no need to restore.
+            return null;
+        }
+
+        final Activity associatedActivity = getActivity(associatedActivityToken);
+        if (associatedActivity == null || associatedActivity.isFinishing()) {
+            return null;
+        }
+
+        return params;
+    }
+
+    /**
      * Called when the {@link WindowContainerTransaction} created with
      * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
      *
@@ -1067,8 +1148,7 @@
             return true;
         }
 
-        // Skip resolving if the activity is on an isolated navigated TaskFragmentContainer.
-        if (container != null && container.isIsolatedNavigationEnabled()) {
+        if (container != null && container.shouldSkipActivityResolving()) {
             return true;
         }
 
@@ -1424,6 +1504,8 @@
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
             mTaskContainers.valueAt(i).onFinishingActivityPaused(wct, activityToken);
         }
+
+        mOverlayRestoreParams.remove(activity.getActivityToken());
         updateCallbackIfNecessary();
     }
 
@@ -1441,6 +1523,8 @@
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
             mTaskContainers.valueAt(i).onActivityDestroyed(wct, activityToken);
         }
+
+        mOverlayRestoreParams.remove(activity.getActivityToken());
         // We didn't trigger the callback if there were any pending appeared activities, so check
         // again after the pending is removed.
         updateCallbackIfNecessary();
@@ -1524,8 +1608,7 @@
             final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity(
                     launchingActivity);
             if (taskFragmentContainer != null
-                    && taskFragmentContainer.isIsolatedNavigationEnabled()) {
-                // Skip resolving if started from an isolated navigated TaskFragmentContainer.
+                    && taskFragmentContainer.shouldSkipActivityResolving()) {
                 return null;
             }
             if (isAssociatedWithOverlay(launchingActivity)) {
@@ -3219,10 +3302,8 @@
             @NonNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer) {
         final DividerPresenter dividerPresenter = mDividerPresenters.get(taskContainer.getTaskId());
         final TaskFragmentParentInfo parentInfo = taskContainer.getTaskFragmentParentInfo();
-        if (parentInfo != null) {
-            dividerPresenter.updateDivider(
-                    wct, parentInfo, taskContainer.getTopNonFinishingSplitContainer());
-        }
+        dividerPresenter.updateDivider(
+                wct, parentInfo, taskContainer.getTopNonFinishingSplitContainer());
     }
 
     @Override
@@ -3243,6 +3324,7 @@
         synchronized (mLock) {
             final TransactionRecord transactionRecord =
                     mTransactionManager.startNewTransaction();
+            transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_DRAG_RESIZE);
             final WindowContainerTransaction wct = transactionRecord.getTransaction();
             final TaskContainer taskContainer = mTaskContainers.get(taskId);
             if (taskContainer != null) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 0e4fb30..2704813 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -401,18 +401,26 @@
             return;
         }
 
-        setTaskFragmentIsolatedNavigation(wct, secondaryContainer, !isStacked /* isolatedNav */);
+        setTaskFragmentPinned(wct, secondaryContainer, !isStacked /* pinned */);
         if (isStacked && !splitPinRule.isSticky()) {
             secondaryContainer.getTaskContainer().removeSplitPinContainer();
         }
     }
 
     /**
-     * Sets whether to enable isolated navigation for this {@link TaskFragmentContainer}
+     * Sets whether to enable isolated navigation for this {@link TaskFragmentContainer}.
+     * <p>
+     * If a container enables isolated navigation, activities can't be launched to this container
+     * unless explicitly requested to be launched to.
+     *
+     * @see TaskFragmentContainer#isOverlayWithActivityAssociation()
      */
     void setTaskFragmentIsolatedNavigation(@NonNull WindowContainerTransaction wct,
                                            @NonNull TaskFragmentContainer container,
                                            boolean isolatedNavigationEnabled) {
+        if (!Flags.activityEmbeddingOverlayPresentationFlag() && container.isOverlay()) {
+            return;
+        }
         if (container.isIsolatedNavigationEnabled() == isolatedNavigationEnabled) {
             return;
         }
@@ -422,6 +430,28 @@
     }
 
     /**
+     * Sets whether to pin this {@link TaskFragmentContainer}.
+     * <p>
+     * If a container is pinned, it won't be chosen as the launch target unless it's the launching
+     * container.
+     *
+     * @see TaskFragmentContainer#isAlwaysOnTopOverlay()
+     * @see TaskContainer#getSplitPinContainer()
+     */
+    void setTaskFragmentPinned(@NonNull WindowContainerTransaction wct,
+                               @NonNull TaskFragmentContainer container,
+                               boolean pinned) {
+        if (!Flags.activityEmbeddingOverlayPresentationFlag() && container.isOverlay()) {
+            return;
+        }
+        if (container.isPinned() == pinned) {
+            return;
+        }
+        container.setPinned(pinned);
+        setTaskFragmentPinned(wct, container.getTaskFragmentToken(), pinned);
+    }
+
+    /**
      * Resizes the task fragment if it was already registered. Skips the operation if the container
      * creation has not been reported from the server yet.
      */
@@ -586,6 +616,11 @@
         super.setCompanionTaskFragment(wct, primary, secondary);
     }
 
+    /**
+     * Applies the {@code attributes} to a standalone {@code container}.
+     *
+     * @param minDimensions the minimum dimension of the container.
+     */
     void applyActivityStackAttributes(
             @NonNull WindowContainerTransaction wct,
             @NonNull TaskFragmentContainer container,
@@ -594,16 +629,17 @@
         final Rect relativeBounds = sanitizeBounds(attributes.getRelativeBounds(), minDimensions,
                 container);
         final boolean isFillParent = relativeBounds.isEmpty();
-        // Note that we only set isolated navigation for overlay container without activity
-        // association. Activity will be launched to an expanded container on top of the overlay
-        // if the overlay is associated with an activity. Thus, an overlay with activity association
-        // will never be isolated navigated.
-        final boolean isIsolatedNavigated = container.isAlwaysOnTopOverlay() && !isFillParent;
         final boolean dimOnTask = !isFillParent
-                && attributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK
-                && Flags.fullscreenDimFlag();
+                && Flags.fullscreenDimFlag()
+                && attributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK;
         final IBinder fragmentToken = container.getTaskFragmentToken();
 
+        if (container.isAlwaysOnTopOverlay()) {
+            setTaskFragmentPinned(wct, container, !isFillParent);
+        } else if (container.isOverlayWithActivityAssociation()) {
+            setTaskFragmentIsolatedNavigation(wct, container, !isFillParent);
+        }
+
         // TODO(b/243518738): Update to resizeTaskFragment after we migrate WCT#setRelativeBounds
         //  and WCT#setWindowingMode to take fragmentToken.
         resizeTaskFragmentIfRegistered(wct, container, relativeBounds);
@@ -612,7 +648,6 @@
         updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode);
         // Always use default animation for standalone ActivityStack.
         updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
-        setTaskFragmentIsolatedNavigation(wct, container, isIsolatedNavigated);
         setTaskFragmentDimOnTask(wct, fragmentToken, dimOnTask);
     }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 67d34c7..c708da9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -78,16 +78,7 @@
     private TaskFragmentContainer mAlwaysOnTopOverlayContainer;
 
     @NonNull
-    private final Configuration mConfiguration;
-
-    private int mDisplayId;
-
-    private boolean mIsVisible;
-
-    private boolean mHasDirectActivity;
-
-    @Nullable
-    private TaskFragmentParentInfo mTaskFragmentParentInfo;
+    private TaskFragmentParentInfo mInfo;
 
     /**
      * TaskFragments that the organizer has requested to be closed. They should be removed when
@@ -131,12 +122,14 @@
         mTaskId = taskId;
         final TaskProperties taskProperties = TaskProperties
                 .getTaskPropertiesFromActivity(activityInTask);
-        mConfiguration = taskProperties.getConfiguration();
-        mDisplayId = taskProperties.getDisplayId();
-        // Note that it is always called when there's a new Activity is started, which implies
-        // the host task is visible and has an activity in the task.
-        mIsVisible = true;
-        mHasDirectActivity = true;
+        mInfo = new TaskFragmentParentInfo(
+                taskProperties.getConfiguration(),
+                taskProperties.getDisplayId(),
+                // Note that it is always called when there's a new Activity is started, which
+                // implies the host task is visible and has an activity in the task.
+                true /* visible */,
+                true /* hasDirectActivity */,
+                null /* decorSurface */);
     }
 
     int getTaskId() {
@@ -144,39 +137,39 @@
     }
 
     int getDisplayId() {
-        return mDisplayId;
+        return mInfo.getDisplayId();
     }
 
     boolean isVisible() {
-        return mIsVisible;
+        return mInfo.isVisible();
+    }
+
+    void setInvisible() {
+        mInfo = new TaskFragmentParentInfo(mInfo.getConfiguration(), mInfo.getDisplayId(),
+                false /* visible */, mInfo.hasDirectActivity(), mInfo.getDecorSurface());
     }
 
     boolean hasDirectActivity() {
-        return mHasDirectActivity;
+        return mInfo.hasDirectActivity();
     }
 
     @NonNull
     Rect getBounds() {
-        return mConfiguration.windowConfiguration.getBounds();
+        return mInfo.getConfiguration().windowConfiguration.getBounds();
     }
 
     @NonNull
     TaskProperties getTaskProperties() {
-        return new TaskProperties(mDisplayId, mConfiguration);
+        return new TaskProperties(mInfo.getDisplayId(), mInfo.getConfiguration());
     }
 
     void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
-        // TODO(b/293654166): cache the TaskFragmentParentInfo and remove these fields.
-        mConfiguration.setTo(info.getConfiguration());
-        mDisplayId = info.getDisplayId();
-        mIsVisible = info.isVisible();
-        mHasDirectActivity = info.hasDirectActivity();
-        mTaskFragmentParentInfo = info;
+        mInfo = info;
     }
 
-    @Nullable
+    @NonNull
     TaskFragmentParentInfo getTaskFragmentParentInfo() {
-        return mTaskFragmentParentInfo;
+        return mInfo;
     }
 
     /**
@@ -185,13 +178,15 @@
     boolean shouldUpdateContainer(@NonNull TaskFragmentParentInfo info) {
         final Configuration configuration = info.getConfiguration();
 
-        return info.isVisible()
-                // No need to update presentation in PIP until the Task exit PIP.
-                && !isInPictureInPicture(configuration)
-                // If the task properties equals regardless of starting position, don't need to
-                // update the container.
-                && (mConfiguration.diffPublicOnly(configuration) != 0
-                || mDisplayId != info.getDisplayId());
+        if (isInPictureInPicture(configuration)) {
+            // No need to update presentation in PIP until the Task exit PIP.
+            return false;
+        }
+
+        // If the task properties equals regardless of starting position, don't
+        // need to update the container.
+        return mInfo.getConfiguration().diffPublicOnly(configuration) != 0
+                || mInfo.getDisplayId() != info.getDisplayId();
     }
 
     /**
@@ -218,7 +213,7 @@
     }
 
     boolean isInPictureInPicture() {
-        return isInPictureInPicture(mConfiguration);
+        return isInPictureInPicture(mInfo.getConfiguration());
     }
 
     private static boolean isInPictureInPicture(@NonNull Configuration configuration) {
@@ -231,7 +226,7 @@
 
     @WindowingMode
     private int getWindowingMode() {
-        return mConfiguration.windowConfiguration.getWindowingMode();
+        return mInfo.getConfiguration().windowConfiguration.getWindowingMode();
     }
 
     /** Whether there is any {@link TaskFragmentContainer} below this Task. */
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index c952dfe..d0b6a01 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -36,6 +36,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -184,6 +185,11 @@
     private boolean mIsIsolatedNavigationEnabled;
 
     /**
+     * Whether this TaskFragment is pinned.
+     */
+    private boolean mIsPinned;
+
+    /**
      * Whether to apply dimming on the parent Task that was requested last.
      */
     private boolean mLastDimOnTask;
@@ -269,6 +275,15 @@
             addPendingAppearedActivity(pendingAppearedActivity);
         }
         mPendingAppearedIntent = pendingAppearedIntent;
+
+        // Save the information necessary for restoring the overlay when needed.
+        if (Flags.fixPipRestoreToOverlay() && overlayTag != null && pendingAppearedIntent != null
+                && associatedActivity != null && !associatedActivity.isFinishing()) {
+            final IBinder associatedActivityToken = associatedActivity.getActivityToken();
+            final OverlayContainerRestoreParams params = new OverlayContainerRestoreParams(mToken,
+                    launchOptions, pendingAppearedIntent);
+            mController.mOverlayRestoreParams.put(associatedActivityToken, params);
+        }
     }
 
     /**
@@ -893,6 +908,34 @@
         mIsIsolatedNavigationEnabled = isolatedNavigationEnabled;
     }
 
+    /**
+     * Returns whether this container is pinned.
+     *
+     * @see android.window.TaskFragmentOperation#OP_TYPE_SET_PINNED
+     */
+    boolean isPinned() {
+        return mIsPinned;
+    }
+
+    /**
+     * Sets whether to pin this container or not.
+     *
+     * @see #isPinned()
+     */
+    void setPinned(boolean pinned) {
+        mIsPinned = pinned;
+    }
+
+    /**
+     * Indicates to skip activity resolving if the activity is from this container.
+     *
+     * @see #isIsolatedNavigationEnabled()
+     * @see #isPinned()
+     */
+    boolean shouldSkipActivityResolving() {
+        return isIsolatedNavigationEnabled() || isPinned();
+    }
+
     /** Sets whether to apply dim on the parent Task. */
     void setLastDimOnTask(boolean lastDimOnTask) {
         mLastDimOnTask = lastDimOnTask;
@@ -1072,4 +1115,25 @@
         }
         return sb.append("]").toString();
     }
+
+    static class OverlayContainerRestoreParams {
+        /** The token of the overlay container */
+        @NonNull
+        final IBinder mOverlayToken;
+
+        /** The launch options to create this container. */
+        @NonNull
+        final Bundle mOptions;
+
+        /** The Intent that used to be started in the overlay container. */
+        @NonNull
+        final Intent mIntent;
+
+        OverlayContainerRestoreParams(@NonNull IBinder overlayToken, @NonNull Bundle options,
+                @NonNull Intent intent) {
+            mOverlayToken = overlayToken;
+            mOptions = options;
+            mIntent = intent;
+        }
+    }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
index 56c3bce..339908a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -16,16 +16,10 @@
 
 package androidx.window.sidecar;
 
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
-import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
-
+import android.annotation.Nullable;
 import android.app.Activity;
-import android.app.ActivityThread;
 import android.app.Application;
 import android.content.Context;
-import android.graphics.Rect;
 import android.hardware.devicestate.DeviceStateManager;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -38,7 +32,6 @@
 import androidx.window.util.BaseDataProducer;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 /**
@@ -76,64 +69,13 @@
     @NonNull
     @Override
     public SidecarDeviceState getDeviceState() {
-        SidecarDeviceState deviceState = new SidecarDeviceState();
-        deviceState.posture = deviceStateFromFeature();
-        return deviceState;
-    }
-
-    private int deviceStateFromFeature() {
-        for (int i = 0; i < mStoredFeatures.size(); i++) {
-            CommonFoldingFeature feature = mStoredFeatures.get(i);
-            final int state = feature.getState();
-            switch (state) {
-                case CommonFoldingFeature.COMMON_STATE_FLAT:
-                    return SidecarDeviceState.POSTURE_OPENED;
-                case CommonFoldingFeature.COMMON_STATE_HALF_OPENED:
-                    return SidecarDeviceState.POSTURE_HALF_OPENED;
-                case CommonFoldingFeature.COMMON_STATE_UNKNOWN:
-                    return SidecarDeviceState.POSTURE_UNKNOWN;
-            }
-        }
-        return SidecarDeviceState.POSTURE_UNKNOWN;
+        return SidecarHelper.calculateDeviceState(mStoredFeatures);
     }
 
     @NonNull
     @Override
     public SidecarWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) {
-        Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken);
-        SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo();
-        if (activity == null) {
-            return windowLayoutInfo;
-        }
-        windowLayoutInfo.displayFeatures = getDisplayFeatures(activity);
-        return windowLayoutInfo;
-    }
-
-    private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
-        int displayId = activity.getDisplay().getDisplayId();
-        if (displayId != DEFAULT_DISPLAY) {
-            return Collections.emptyList();
-        }
-
-        if (activity.isInMultiWindowMode()) {
-            // It is recommended not to report any display features in multi-window mode, since it
-            // won't be possible to synchronize the display feature positions with window movement.
-            return Collections.emptyList();
-        }
-
-        List<SidecarDisplayFeature> features = new ArrayList<>();
-        final int rotation = activity.getResources().getConfiguration().windowConfiguration
-                .getDisplayRotation();
-        for (CommonFoldingFeature baseFeature : mStoredFeatures) {
-            SidecarDisplayFeature feature = new SidecarDisplayFeature();
-            Rect featureRect = baseFeature.getRect();
-            rotateRectToDisplayRotation(displayId, rotation, featureRect);
-            transformToWindowSpaceRect(activity, featureRect);
-            feature.setRect(featureRect);
-            feature.setType(baseFeature.getType());
-            features.add(feature);
-        }
-        return Collections.unmodifiableList(features);
+        return SidecarHelper.calculateWindowLayoutInfo(windowToken, mStoredFeatures);
     }
 
     @Override
@@ -145,13 +87,14 @@
 
     private final class NotifyOnConfigurationChanged extends EmptyLifecycleCallbacksAdapter {
         @Override
-        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+        public void onActivityCreated(@NonNull Activity activity,
+                @Nullable Bundle savedInstanceState) {
             super.onActivityCreated(activity, savedInstanceState);
             onDisplayFeaturesChangedForActivity(activity);
         }
 
         @Override
-        public void onActivityConfigurationChanged(Activity activity) {
+        public void onActivityConfigurationChanged(@NonNull Activity activity) {
             super.onActivityConfigurationChanged(activity);
             onDisplayFeaturesChangedForActivity(activity);
         }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java
new file mode 100644
index 0000000..bb6ab47
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java
@@ -0,0 +1,129 @@
+/*
+ * 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 androidx.window.sidecar;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
+import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.app.ActivityThread;
+import android.graphics.Rect;
+import android.os.IBinder;
+
+import androidx.window.common.CommonFoldingFeature;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A utility class for transforming between Sidecar and Extensions features.
+ */
+class SidecarHelper {
+
+    private SidecarHelper() {}
+
+    /**
+     * Returns the {@link SidecarDeviceState} posture that is calculated for the first fold in
+     * the feature list. Sidecar devices only have one fold so we only pick the first one to
+     * determine the state.
+     * @param featureList the {@link CommonFoldingFeature} that are currently active.
+     * @return the {@link SidecarDeviceState} calculated from the {@link List} of
+     * {@link CommonFoldingFeature}.
+     */
+    @SuppressWarnings("deprecation")
+    private static int deviceStateFromFeatureList(@NonNull List<CommonFoldingFeature> featureList) {
+        for (int i = 0; i < featureList.size(); i++) {
+            final CommonFoldingFeature feature = featureList.get(i);
+            final int state = feature.getState();
+            switch (state) {
+                case CommonFoldingFeature.COMMON_STATE_FLAT:
+                    return SidecarDeviceState.POSTURE_OPENED;
+                case CommonFoldingFeature.COMMON_STATE_HALF_OPENED:
+                    return SidecarDeviceState.POSTURE_HALF_OPENED;
+                case CommonFoldingFeature.COMMON_STATE_UNKNOWN:
+                    return SidecarDeviceState.POSTURE_UNKNOWN;
+                case CommonFoldingFeature.COMMON_STATE_NO_FOLDING_FEATURES:
+                    return SidecarDeviceState.POSTURE_UNKNOWN;
+                case CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE:
+                    return SidecarDeviceState.POSTURE_UNKNOWN;
+            }
+        }
+        return SidecarDeviceState.POSTURE_UNKNOWN;
+    }
+
+    /**
+     * Returns a {@link SidecarDeviceState} calculated from a {@link List} of
+     * {@link CommonFoldingFeature}s.
+     */
+    @SuppressWarnings("deprecation")
+    static SidecarDeviceState calculateDeviceState(
+            @NonNull List<CommonFoldingFeature> featureList) {
+        final SidecarDeviceState deviceState = new SidecarDeviceState();
+        deviceState.posture = deviceStateFromFeatureList(featureList);
+        return deviceState;
+    }
+
+    @SuppressWarnings("deprecation")
+    private static List<SidecarDisplayFeature> calculateDisplayFeatures(
+            @NonNull Activity activity,
+            @NonNull List<CommonFoldingFeature> featureList
+    ) {
+        final int displayId = activity.getDisplay().getDisplayId();
+        if (displayId != DEFAULT_DISPLAY) {
+            return Collections.emptyList();
+        }
+
+        if (activity.isInMultiWindowMode()) {
+            // It is recommended not to report any display features in multi-window mode, since it
+            // won't be possible to synchronize the display feature positions with window movement.
+            return Collections.emptyList();
+        }
+
+        final List<SidecarDisplayFeature> features = new ArrayList<>();
+        final int rotation = activity.getResources().getConfiguration().windowConfiguration
+                .getDisplayRotation();
+        for (CommonFoldingFeature baseFeature : featureList) {
+            final SidecarDisplayFeature feature = new SidecarDisplayFeature();
+            final Rect featureRect = baseFeature.getRect();
+            rotateRectToDisplayRotation(displayId, rotation, featureRect);
+            transformToWindowSpaceRect(activity, featureRect);
+            feature.setRect(featureRect);
+            feature.setType(baseFeature.getType());
+            features.add(feature);
+        }
+        return Collections.unmodifiableList(features);
+    }
+
+    /**
+     * Returns a {@link SidecarWindowLayoutInfo} calculated from the {@link List} of
+     * {@link CommonFoldingFeature}.
+     */
+    @SuppressWarnings("deprecation")
+    static SidecarWindowLayoutInfo calculateWindowLayoutInfo(@NonNull IBinder windowToken,
+            @NonNull List<CommonFoldingFeature> featureList) {
+        final Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken);
+        final SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo();
+        if (activity == null) {
+            return windowLayoutInfo;
+        }
+        windowLayoutInfo.displayFeatures = calculateDisplayFeatures(activity, featureList);
+        return windowLayoutInfo;
+    }
+}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
index 8aca92e..746607c 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
@@ -19,6 +19,10 @@
 import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE;
 import static android.window.TaskFragmentOperation.OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE;
 
+import static androidx.window.extensions.embedding.DividerPresenter.FLING_ANIMATION_DURATION;
+import static androidx.window.extensions.embedding.DividerPresenter.FLING_ANIMATION_INTERPOLATOR;
+import static androidx.window.extensions.embedding.DividerPresenter.MIN_DISMISS_VELOCITY_DP_PER_SECOND;
+import static androidx.window.extensions.embedding.DividerPresenter.MIN_FLING_VELOCITY_DP_PER_SECOND;
 import static androidx.window.extensions.embedding.DividerPresenter.getBoundsOffsetForDivider;
 import static androidx.window.extensions.embedding.DividerPresenter.getInitialDividerPosition;
 import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_BOTTOM;
@@ -35,8 +39,12 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.animation.ValueAnimator;
+import android.app.Activity;
 import android.content.res.Configuration;
+import android.graphics.Color;
 import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
 import android.os.Binder;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
@@ -44,6 +52,8 @@
 import android.view.Display;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
+import android.view.View;
+import android.view.Window;
 import android.window.TaskFragmentOperation;
 import android.window.TaskFragmentParentInfo;
 import android.window.WindowContainerTransaction;
@@ -152,7 +162,10 @@
                 true /* isVerticalSplit */,
                 false /* isReversedLayout */,
                 Display.DEFAULT_DISPLAY,
-                false /* isDraggableExpandType */);
+                false /* isDraggableExpandType */,
+                Color.valueOf(Color.BLACK), /* primaryVeilColor */
+                Color.valueOf(Color.GRAY) /* secondaryVeilColor */
+        );
 
         mDividerPresenter = new DividerPresenter(
                 MOCK_TASK_ID, mDragEventCallback, mock(Executor.class));
@@ -604,6 +617,130 @@
                 0.0001 /* delta */);
     }
 
+    @Test
+    public void testGetContainerBackgroundColor() {
+        final Color defaultColor = Color.valueOf(Color.RED);
+        final Color activityBackgroundColor = Color.valueOf(Color.BLUE);
+        final TaskFragmentContainer container = mock(TaskFragmentContainer.class);
+        final Activity activity = mock(Activity.class);
+        final Window window = mock(Window.class);
+        final View decorView = mock(View.class);
+        final ColorDrawable backgroundDrawable =
+                new ColorDrawable(activityBackgroundColor.toArgb());
+        when(activity.getWindow()).thenReturn(window);
+        when(window.getDecorView()).thenReturn(decorView);
+        when(decorView.getBackground()).thenReturn(backgroundDrawable);
+
+        // When the top non-finishing activity returns null, the default color should be returned.
+        when(container.getTopNonFinishingActivity()).thenReturn(null);
+        assertEquals(defaultColor,
+                DividerPresenter.getContainerBackgroundColor(container, defaultColor));
+
+        // When the top non-finishing activity is non-null, its background color should be returned.
+        when(container.getTopNonFinishingActivity()).thenReturn(activity);
+        assertEquals(activityBackgroundColor,
+                DividerPresenter.getContainerBackgroundColor(container, defaultColor));
+    }
+
+    @Test
+    public void testGetValueAnimator() {
+        ValueAnimator animator =
+                DividerPresenter.getValueAnimator(
+                        375 /* prevDividerPosition */,
+                        500 /* snappedDividerPosition */);
+
+        assertEquals(animator.getDuration(), FLING_ANIMATION_DURATION);
+        assertEquals(animator.getInterpolator(), FLING_ANIMATION_INTERPOLATOR);
+    }
+
+    @Test
+    public void testDividerPositionWithDraggingToFullscreenAllowed() {
+        final float displayDensity = 600F;
+        final float dismissVelocity = MIN_DISMISS_VELOCITY_DP_PER_SECOND * displayDensity + 10f;
+        final float nonFlingVelocity = MIN_FLING_VELOCITY_DP_PER_SECOND * displayDensity - 10f;
+        final float flingVelocity = MIN_FLING_VELOCITY_DP_PER_SECOND * displayDensity + 10f;
+
+        // Divider position is less than minPosition and the velocity is enough to be dismissed
+        assertEquals(
+                0, // Closed position
+                DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed(
+                        10 /* dividerPosition */,
+                        30 /* minPosition */,
+                        900 /* maxPosition */,
+                        1200 /* fullyExpandedPosition */,
+                        -dismissVelocity,
+                        displayDensity));
+
+        // Divider position is greater than maxPosition and the velocity is enough to be dismissed
+        assertEquals(
+                1200, // Fully expanded position
+                DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed(
+                        1000 /* dividerPosition */,
+                        30 /* minPosition */,
+                        900 /* maxPosition */,
+                        1200 /* fullyExpandedPosition */,
+                        dismissVelocity,
+                        displayDensity));
+
+        // Divider position is returned when the velocity is not fast enough for fling and is in
+        // between minPosition and maxPosition
+        assertEquals(
+                500, // dividerPosition is not snapped
+                DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed(
+                        500 /* dividerPosition */,
+                        30 /* minPosition */,
+                        900 /* maxPosition */,
+                        1200 /* fullyExpandedPosition */,
+                        nonFlingVelocity,
+                        displayDensity));
+
+        // Divider position is snapped when the velocity is not fast enough for fling and larger
+        // than maxPosition
+        assertEquals(
+                900, // Closest position is maxPosition
+                DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed(
+                        950 /* dividerPosition */,
+                        30 /* minPosition */,
+                        900 /* maxPosition */,
+                        1200 /* fullyExpandedPosition */,
+                        nonFlingVelocity,
+                        displayDensity));
+
+        // Divider position is snapped when the velocity is not fast enough for fling and smaller
+        // than minPosition
+        assertEquals(
+                30, // Closest position is minPosition
+                DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed(
+                        20 /* dividerPosition */,
+                        30 /* minPosition */,
+                        900 /* maxPosition */,
+                        1200 /* fullyExpandedPosition */,
+                        nonFlingVelocity,
+                        displayDensity));
+
+        // Divider position is greater than minPosition and the velocity is enough for fling
+        assertEquals(
+                0, // Closed position
+                DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed(
+                        50 /* dividerPosition */,
+                        30 /* minPosition */,
+                        900 /* maxPosition */,
+                        1200 /* fullyExpandedPosition */,
+                        -flingVelocity,
+                        displayDensity));
+
+        // Divider position is less than maxPosition and the velocity is enough for fling
+        assertEquals(
+                1200, // Fully expanded position
+                DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed(
+                        800 /* dividerPosition */,
+                        30 /* minPosition */,
+                        900 /* maxPosition */,
+                        1200 /* fullyExpandedPosition */,
+                        flingVelocity,
+                        displayDensity));
+    }
+
     private TaskFragmentContainer createMockTaskFragmentContainer(
             @NonNull IBinder token, @NonNull Rect bounds) {
         final TaskFragmentContainer container = mock(TaskFragmentContainer.class);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index fab298d..f322257 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -188,6 +188,32 @@
     }
 
     @Test
+    public void testSetIsolatedNavigation_overlayFeatureDisabled_earlyReturn() {
+        mSetFlagRule.disableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
+
+        final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, "test");
+
+        mSplitPresenter.setTaskFragmentIsolatedNavigation(mTransaction, container,
+                !container.isIsolatedNavigationEnabled());
+
+        verify(mSplitPresenter, never()).setTaskFragmentIsolatedNavigation(any(),
+                any(IBinder.class), anyBoolean());
+    }
+
+    @Test
+    public void testSetPinned_overlayFeatureDisabled_earlyReturn() {
+        mSetFlagRule.disableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
+
+        final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, "test");
+
+        mSplitPresenter.setTaskFragmentPinned(mTransaction, container,
+                !container.isPinned());
+
+        verify(mSplitPresenter, never()).setTaskFragmentPinned(any(), any(IBinder.class),
+                anyBoolean());
+    }
+
+    @Test
     public void testGetAllNonFinishingOverlayContainers() {
         assertThat(mSplitController.getAllNonFinishingOverlayContainers()).isEmpty();
 
@@ -580,7 +606,7 @@
         final TaskContainer.TaskProperties taskProperties = taskContainer.getTaskProperties();
         final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(
                 new Configuration(taskProperties.getConfiguration()), taskProperties.getDisplayId(),
-                false /* visible */, false /* hasDirectActivity */, null /* decorSurface */);
+                true /* visible */, false /* hasDirectActivity */, null /* decorSurface */);
 
         mSplitController.onTaskFragmentParentInfoChanged(mTransaction, TASK_ID, parentInfo);
 
@@ -608,8 +634,11 @@
                 WINDOWING_MODE_UNDEFINED);
         verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
                 TaskFragmentAnimationParams.DEFAULT);
-        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
         verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
+        verify(mSplitPresenter, never()).setTaskFragmentPinned(any(),
+                any(TaskFragmentContainer.class), anyBoolean());
+        verify(mSplitPresenter, never()).setTaskFragmentIsolatedNavigation(any(),
+                any(TaskFragmentContainer.class), anyBoolean());
     }
 
     @Test
@@ -630,9 +659,9 @@
                 WINDOWING_MODE_MULTI_WINDOW);
         verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
                 TaskFragmentAnimationParams.DEFAULT);
-        // Set isolated navigation to false if the overlay container is associated with
-        // the launching activity.
-        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, true);
+        verify(mSplitPresenter, never()).setTaskFragmentPinned(any(),
+                any(TaskFragmentContainer.class), anyBoolean());
         verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, true);
     }
 
@@ -655,10 +684,9 @@
                 container, WINDOWING_MODE_MULTI_WINDOW);
         verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
                 TaskFragmentAnimationParams.DEFAULT);
-        // Set isolated navigation to false if the overlay container is associated with
-        // the launching activity.
-        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction,
-                container, true);
+        verify(mSplitPresenter, never()).setTaskFragmentIsolatedNavigation(any(),
+                any(TaskFragmentContainer.class), anyBoolean());
+        verify(mSplitPresenter).setTaskFragmentPinned(mTransaction, container, true);
         verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, true);
     }
 
@@ -678,6 +706,8 @@
         verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
                 TaskFragmentAnimationParams.DEFAULT);
         verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+        verify(mSplitPresenter, never()).setTaskFragmentPinned(any(),
+                any(TaskFragmentContainer.class), anyBoolean());
         verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
     }
 
@@ -806,6 +836,30 @@
                 any());
     }
 
+    @Test
+    public void testOnActivityReparentedToTask_overlayRestoration() {
+        mSetFlagRule.enableFlags(Flags.FLAG_FIX_PIP_RESTORE_TO_OVERLAY);
+
+        // Prepares and mock the data necessary for the test.
+        final IBinder activityToken = mActivity.getActivityToken();
+        final Intent intent = new Intent();
+        final IBinder fillTaskActivityToken = new Binder();
+        final IBinder lastOverlayToken = new Binder();
+        final TaskFragmentContainer overlayContainer = mSplitController.newContainer(intent,
+                mActivity, TASK_ID);
+        final TaskFragmentContainer.OverlayContainerRestoreParams params = mock(
+                TaskFragmentContainer.OverlayContainerRestoreParams.class);
+        doReturn(params).when(mSplitController).getOverlayContainerRestoreParams(any(), any());
+        doReturn(overlayContainer).when(mSplitController).createOrUpdateOverlayTaskFragmentIfNeeded(
+                any(), any(), any(), any());
+
+        // Verify the activity should be reparented to the overlay container.
+        mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, intent, activityToken,
+                fillTaskActivityToken, lastOverlayToken);
+        verify(mTransaction).reparentActivityToTaskFragment(
+                eq(overlayContainer.getTaskFragmentToken()), eq(activityToken));
+    }
+
     /**
      * A simplified version of {@link SplitController#createOrUpdateOverlayTaskFragmentIfNeeded}
      */
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index a525877..35353db 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -397,7 +397,8 @@
     @Test
     public void testOnActivityReparentedToTask_sameProcess() {
         mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, new Intent(),
-                mActivity.getActivityToken());
+                mActivity.getActivityToken(), null /* fillTaskActivityToken */,
+                null /* lastOverlayToken */);
 
         // Treated as on activity created, but allow to split as primary.
         verify(mSplitController).resolveActivityToContainer(mTransaction,
@@ -413,7 +414,8 @@
         final IBinder activityToken = new Binder();
         final Intent intent = new Intent();
 
-        mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, intent, activityToken);
+        mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, intent, activityToken,
+                null /* fillTaskActivityToken */, null /* lastOverlayToken */);
 
         // Treated as starting new intent
         verify(mSplitController, never()).resolveActivityToContainer(any(), any(), anyBoolean());
@@ -1210,7 +1212,7 @@
         mSplitController.onTransactionReady(transaction);
 
         verify(mSplitController).onActivityReparentedToTask(any(), eq(TASK_ID), eq(intent),
-                eq(activityToken));
+                eq(activityToken), any(), any());
         verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(),
                 anyInt(), anyBoolean());
     }
@@ -1619,6 +1621,48 @@
         verify(mEmbeddedActivityWindowInfoCallback, never()).accept(any());
     }
 
+    @Test
+    public void testTaskFragmentParentInfoChanged() {
+        // Making a split
+        final Activity secondaryActivity = createMockActivity();
+        addSplitTaskFragments(mActivity, secondaryActivity, false /* clearTop */);
+
+        // Updates the parent info.
+        final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+        final Configuration configuration = new Configuration();
+        final TaskFragmentParentInfo originalInfo = new TaskFragmentParentInfo(configuration,
+                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+                null /* decorSurface */);
+        mSplitController.onTaskFragmentParentInfoChanged(mock(WindowContainerTransaction.class),
+                TASK_ID, originalInfo);
+        assertTrue(taskContainer.isVisible());
+
+        // Making a public configuration change while the Task is invisible.
+        configuration.densityDpi += 100;
+        final TaskFragmentParentInfo invisibleInfo = new TaskFragmentParentInfo(configuration,
+                DEFAULT_DISPLAY, false /* visible */, false /* hasDirectActivity */,
+                null /* decorSurface */);
+        mSplitController.onTaskFragmentParentInfoChanged(mock(WindowContainerTransaction.class),
+                TASK_ID, invisibleInfo);
+
+        // Ensure the TaskContainer is inivisible, but the configuration is not updated.
+        assertFalse(taskContainer.isVisible());
+        assertTrue(taskContainer.getTaskFragmentParentInfo().getConfiguration().diffPublicOnly(
+                configuration) > 0);
+
+        // Updates when Task to become visible
+        final TaskFragmentParentInfo visibleInfo = new TaskFragmentParentInfo(configuration,
+                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+                null /* decorSurface */);
+        mSplitController.onTaskFragmentParentInfoChanged(mock(WindowContainerTransaction.class),
+                TASK_ID, visibleInfo);
+
+        // Ensure the Task is visible and configuration is updated.
+        assertTrue(taskContainer.isVisible());
+        assertFalse(taskContainer.getTaskFragmentParentInfo().getConfiguration().diffPublicOnly(
+                configuration) > 0);
+    }
+
     /** Creates a mock activity in the organizer process. */
     private Activity createMockActivity() {
         return createMockActivity(TASK_ID);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index c677484..3fbce9ec 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_PINNED;
 
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY;
@@ -285,6 +286,28 @@
     }
 
     @Test
+    public void testSetTaskFragmentPinned() {
+        final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID);
+
+        // Verify the default.
+        assertFalse(container.isPinned());
+
+        mPresenter.setTaskFragmentPinned(mTransaction, container, true);
+
+        final TaskFragmentOperation expectedOperation = new TaskFragmentOperation.Builder(
+                OP_TYPE_SET_PINNED).setBooleanValue(true).build();
+        verify(mTransaction).addTaskFragmentOperation(container.getTaskFragmentToken(),
+                expectedOperation);
+        assertTrue(container.isPinned());
+
+        // No request to set the same animation params.
+        clearInvocations(mTransaction);
+        mPresenter.setTaskFragmentPinned(mTransaction, container, true);
+
+        verify(mTransaction, never()).addTaskFragmentOperation(any(), any());
+    }
+
+    @Test
     public void testGetMinDimensionsForIntent() {
         final Intent intent = new Intent(ApplicationProvider.getApplicationContext(),
                 MinimumDimensionActivity.class);
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 7ff204c..8977d5c 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -64,3 +64,17 @@
     description: "Enables long-press action for nav handle when a bubble is expanded"
     bug: "324910035"
 }
+
+flag {
+    name: "enable_optional_bubble_overflow"
+    namespace: "multitasking"
+    description: "Hides the bubble overflow if there aren't any overflowed bubbles"
+    bug: "334175587"
+}
+
+flag {
+    name: "enable_retrievable_bubbles"
+    namespace: "multitasking"
+    description: "Allow opening bubbles overflow UI without bubbles being visible"
+    bug: "340337839"
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
index 6110133..0764141 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
@@ -31,7 +31,6 @@
 import com.android.wm.shell.R
 import com.android.wm.shell.bubbles.BubblePositioner
 import com.android.wm.shell.bubbles.DeviceConfig
-import com.android.wm.shell.bubbles.bar.BubbleExpandedViewPinController.Companion.DROP_TARGET_SCALE
 import com.android.wm.shell.common.bubbles.BaseBubblePinController
 import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_IN_DURATION
 import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_OUT_DURATION
@@ -248,15 +247,8 @@
     private val dropTargetView: View?
         get() = container.findViewById(R.id.bubble_bar_drop_target)
 
-    private fun getExpectedDropTargetBounds(onLeft: Boolean): Rect {
-        val rect = Rect()
-        positioner.getBubbleBarExpandedViewBounds(onLeft, false /* isOveflowExpanded */, rect)
-        // Scale the rect to expected size, but keep the center point the same
-        val centerX = rect.centerX()
-        val centerY = rect.centerY()
-        rect.scale(DROP_TARGET_SCALE)
-        rect.offset(centerX - rect.centerX(), centerY - rect.centerY())
-        return rect
+    private fun getExpectedDropTargetBounds(onLeft: Boolean): Rect = Rect().also {
+        positioner.getBubbleBarExpandedViewBounds(onLeft, false /* isOveflowExpanded */, it)
     }
 
     private fun runOnMainSync(runnable: Runnable) {
diff --git a/libs/WindowManager/Shell/res/drawable/bubble_drop_target_background.xml b/libs/WindowManager/Shell/res/drawable/bubble_drop_target_background.xml
index 9dcde3b..b928a0b 100644
--- a/libs/WindowManager/Shell/res/drawable/bubble_drop_target_background.xml
+++ b/libs/WindowManager/Shell/res/drawable/bubble_drop_target_background.xml
@@ -13,12 +13,14 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:shape="rectangle">
-    <corners android:radius="@dimen/bubble_bar_expanded_view_corner_radius" />
-    <solid android:color="@color/bubble_drop_target_background_color" />
-    <stroke
-        android:width="1dp"
-        android:color="?androidprv:attr/materialColorPrimaryContainer" />
-</shape>
+    android:inset="@dimen/bubble_bar_expanded_view_drop_target_padding">
+    <shape android:shape="rectangle">
+        <corners android:radius="@dimen/bubble_bar_expanded_view_drop_target_corner" />
+        <solid android:color="@color/bubble_drop_target_background_color" />
+        <stroke
+            android:width="1dp"
+            android:color="?androidprv:attr/materialColorPrimaryContainer" />
+    </shape>
+</inset>
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index b9ff5c6..1c8f5e6 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -53,7 +53,7 @@
     <string name="accessibility_split_top" msgid="2789329702027147146">"Verdeel bo"</string>
     <string name="accessibility_split_bottom" msgid="8694551025220868191">"Verdeel onder"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Gebruik eenhandmodus"</string>
-    <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Swiep van die onderkant van die skerm af op of tik enige plek bo die program om uit te gaan"</string>
+    <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Swiep van die onderkant van die skerm af op of tik enige plek bo die app om uit te gaan"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Begin eenhandmodus"</string>
     <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Verlaat eenhandmodus"</string>
     <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Instellings vir <xliff:g id="APP_NAME">%1$s</xliff:g>-borrels"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index 1fde4cf..3492f13 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -53,7 +53,7 @@
     <string name="accessibility_split_top" msgid="2789329702027147146">"Diviser dans la partie supérieure"</string>
     <string name="accessibility_split_bottom" msgid="8694551025220868191">"Diviser dans la partie inférieure"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Utiliser le mode Une main"</string>
-    <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pour quitter, balayez l\'écran du bas vers le haut, ou touchez n\'importe où sur l\'écran en haut de l\'application"</string>
+    <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pour quitter, balayez l\'écran du bas vers le haut, ou touchez n\'importe où sur l\'écran en haut de l\'appli"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Démarrer le mode Une main"</string>
     <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Quitter le mode Une main"</string>
     <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Paramètres pour les bulles de l\'application <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
@@ -62,9 +62,9 @@
     <string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
     <string name="bubble_content_description_stack" msgid="8071515017164630429">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g> et <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g> autres"</string>
     <string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"Déplacer dans coin sup. gauche"</string>
-    <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Déplacer dans coin sup. droit"</string>
-    <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Déplacer dans coin inf. gauche"</string>
-    <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Déplacer dans coin inf. droit"</string>
+    <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Déplacer en haut à droite"</string>
+    <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Déplacer en bas à gauche"</string>
+    <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Déplacer en bas à droite"</string>
     <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"développer <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
     <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"réduire <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
     <string name="bubbles_app_settings" msgid="3617224938701566416">"Paramètres <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index e7233ae..4002e4d 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -52,7 +52,7 @@
     <string name="accessibility_split_right" msgid="8441001008181296837">"Affichée à droite"</string>
     <string name="accessibility_split_top" msgid="2789329702027147146">"Affichée en haut"</string>
     <string name="accessibility_split_bottom" msgid="8694551025220868191">"Affichée en haut"</string>
-    <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Utiliser le mode une main"</string>
+    <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Utilisation du mode une main"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pour quitter, balayez l\'écran de bas en haut ou appuyez n\'importe où au-dessus de l\'application"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Démarrer le mode une main"</string>
     <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Quitter le mode une main"</string>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index fc39420..27d4cfc 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -52,7 +52,7 @@
     <string name="accessibility_split_right" msgid="8441001008181296837">"Podijeli desno"</string>
     <string name="accessibility_split_top" msgid="2789329702027147146">"Podijeli gore"</string>
     <string name="accessibility_split_bottom" msgid="8694551025220868191">"Podijeli dolje"</string>
-    <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Korištenje načina rada jednom rukom"</string>
+    <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Upotreba načina rada jednom rukom"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Za izlaz prijeđite prstom od dna zaslona prema gore ili dodirnite bio gdje iznad aplikacije"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Pokretanje načina rada jednom rukom"</string>
     <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Izlaz iz načina rada jednom rukom"</string>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index 7b7779d..302c007 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -71,7 +71,7 @@
     <string name="bubble_dismiss_text" msgid="8816558050659478158">"Калкып чыкма билдирмени жабуу"</string>
     <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Жазышууда калкып чыкма билдирмелер көрүнбөсүн"</string>
     <string name="bubbles_user_education_title" msgid="2112319053732691899">"Калкып чыкма билдирмелер аркылуу маектешүү"</string>
-    <string name="bubbles_user_education_description" msgid="4215862563054175407">"Жаңы жазышуулар калкыма сүрөтчөлөр же калкып чыкма билдирмелер түрүндө көрүнөт. Калкып чыкма билдирмелерди ачуу үчүн таптап коюңуз. Жылдыруу үчүн сүйрөңүз."</string>
+    <string name="bubbles_user_education_description" msgid="4215862563054175407">"Жаңы жазышуулар калкыма сүрөтчөлөр же калкып чыкма билдирмелер түрүндө көрүнөт. Калкып чыкма билдирмелерди ачуу үчүн тийип коюңуз. Жылдыруу үчүн сүйрөңүз."</string>
     <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Калкып чыкма билдирмелерди каалаган убакта көзөмөлдөңүз"</string>
     <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Бул колдонмодогу калкып чыкма билдирмелерди өчүрүү үчүн \"Башкарууну\" басыңыз"</string>
     <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Түшүндүм"</string>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index 4a9fab9..5e43506 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -53,7 +53,7 @@
     <string name="accessibility_split_top" msgid="2789329702027147146">"Дээд талд хуваах"</string>
     <string name="accessibility_split_bottom" msgid="8694551025220868191">"Доод талд хуваах"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Нэг гарын горимыг ашиглаж байна"</string>
-    <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Гарахын тулд дэлгэцийн доод хэсгээс дээш шударч эсвэл апп дээр хүссэн газраа товшино уу"</string>
+    <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Гарахын тулд дэлгэцийн доод хэсгээс дээш шударч эсвэл аппын дээр хүссэн газраа товшино уу"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Нэг гарын горимыг эхлүүлэх"</string>
     <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Нэг гарын горимоос гарах"</string>
     <string name="bubbles_settings_button_description" msgid="1301286017420516912">"<xliff:g id="APP_NAME">%1$s</xliff:g>-н бөмбөлгүүдийн тохиргоо"</string>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index f532f96..aa4fb44 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -247,13 +247,11 @@
     <!-- Padding for the bubble popup view contents. -->
     <dimen name="bubble_popup_padding">24dp</dimen>
     <!-- The size of the caption bar inset at the top of bubble bar expanded view. -->
-    <dimen name="bubble_bar_expanded_view_caption_height">32dp</dimen>
+    <dimen name="bubble_bar_expanded_view_caption_height">36dp</dimen>
     <!-- The width of the caption bar at the top of bubble bar expanded view. -->
-    <dimen name="bubble_bar_expanded_view_caption_width">128dp</dimen>
-    <!-- The height of the dots shown for the caption menu in the bubble bar expanded view.. -->
-    <dimen name="bubble_bar_expanded_view_caption_dot_size">4dp</dimen>
-    <!-- The spacing between the dots for the caption menu in the bubble bar expanded view.. -->
-    <dimen name="bubble_bar_expanded_view_caption_dot_spacing">4dp</dimen>
+    <dimen name="bubble_bar_expanded_view_caption_width">80dp</dimen>
+    <!-- The height of the handle shown for the caption menu in the bubble bar expanded view. -->
+    <dimen name="bubble_bar_expanded_view_handle_height">4dp</dimen>
     <!-- Width of the expanded bubble bar view shown when the bubble is expanded. -->
     <dimen name="bubble_bar_expanded_view_width">412dp</dimen>
     <!-- Minimum width of the bubble bar manage menu. -->
@@ -274,6 +272,9 @@
     <dimen name="bubble_bar_expanded_view_corner_radius">16dp</dimen>
     <!-- Corner radius for expanded view while it is being dragged -->
     <dimen name="bubble_bar_expanded_view_corner_radius_dragged">28dp</dimen>
+    <!-- Corner radius for expanded view drop target -->
+    <dimen name="bubble_bar_expanded_view_drop_target_corner">28dp</dimen>
+    <dimen name="bubble_bar_expanded_view_drop_target_padding">24dp</dimen>
     <!-- Width of the box around bottom center of the screen where drag only leads to dismiss -->
     <dimen name="bubble_bar_dismiss_zone_width">192dp</dimen>
     <!-- Height of the box around bottom center of the screen where drag only leads to dismiss -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java
similarity index 82%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java
index fcddcad..bdd89c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.desktopmode;
+package com.android.wm.shell.shared;
 
 import android.annotation.NonNull;
 import android.content.Context;
@@ -41,15 +41,6 @@
     public static final boolean IS_DISPLAY_CHANGE_ENABLED = SystemProperties.getBoolean(
             "persist.wm.debug.desktop_change_display", false);
 
-
-    /**
-     * Flag to indicate that desktop stashing is enabled.
-     * When enabled, swiping home from desktop stashes the open apps. Next app that launches,
-     * will be added to the desktop.
-     */
-    private static final boolean IS_STASHING_ENABLED = SystemProperties.getBoolean(
-            "persist.wm.debug.desktop_stashing", false);
-
     /**
      * Flag to indicate whether to apply shadows to windows in desktop mode.
      */
@@ -65,7 +56,7 @@
             "persist.wm.debug.desktop_use_window_shadows_focused_window", false);
 
     /**
-     * Flag to indicate whether to apply shadows to windows in desktop mode.
+     * Flag to indicate whether to use rounded corners for windows in desktop mode.
      */
     private static final boolean USE_ROUNDED_CORNERS = SystemProperties.getBoolean(
             "persist.wm.debug.desktop_use_rounded_corners", true);
@@ -93,8 +84,10 @@
             "persist.wm.debug.desktop_max_task_limit", DEFAULT_MAX_TASK_LIMIT);
 
     /**
-     * Return {@code true} if desktop windowing is enabled
+     * Return {@code true} if desktop windowing is enabled. Only to be used for testing. Callers
+     * should use {@link #canEnterDesktopMode(Context)} to query the state of desktop windowing.
      */
+    @VisibleForTesting
     public static boolean isEnabled() {
         return Flags.enableDesktopWindowingMode();
     }
@@ -107,14 +100,6 @@
     }
 
     /**
-     * Return {@code true} if desktop task stashing is enabled when going home.
-     * Allows users to use home screen to add tasks to desktop.
-     */
-    public static boolean isStashingEnabled() {
-        return IS_STASHING_ENABLED;
-    }
-
-    /**
      * Return whether to use window shadows.
      *
      * @param isFocusedWindow whether the window to apply shadows to is focused
@@ -142,7 +127,7 @@
     /**
      * Return the maximum limit on the number of Tasks to show in Desktop Mode at any one time.
      */
-    static int getMaxTaskLimit() {
+    public static int getMaxTaskLimit() {
         return MAX_TASK_LIMIT;
     }
 
@@ -155,9 +140,9 @@
     }
 
     /**
-     * Return {@code true} if desktop mode can be entered on the current device.
+     * Return {@code true} if desktop mode is enabled and can be entered on the current device.
      */
     public static boolean canEnterDesktopMode(@NonNull Context context) {
-        return !enforceDeviceRestrictions() || isDesktopModeSupported(context);
+        return (!enforceDeviceRestrictions() || isDesktopModeSupported(context)) && isEnabled();
     }
 }
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index dcd4062..785e30d 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -69,8 +69,12 @@
 
     /** Returns {@code true} if the transition is opening or closing mode. */
     public static boolean isOpenOrCloseMode(@TransitionInfo.TransitionMode int mode) {
-        return mode == TRANSIT_OPEN || mode == TRANSIT_CLOSE
-                || mode == TRANSIT_TO_FRONT || mode == TRANSIT_TO_BACK;
+        return isOpeningMode(mode) || mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK;
+    }
+
+    /** Returns {@code true} if the transition is opening mode. */
+    public static boolean isOpeningMode(@TransitionInfo.TransitionMode int mode) {
+        return mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT;
     }
 
     /** Returns {@code true} if the transition has a display change. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index d44033c..a426b20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -26,6 +26,7 @@
 import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE;
 
 import android.animation.Animator;
 import android.animation.ValueAnimator;
@@ -190,6 +191,10 @@
     @NonNull
     private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters(
             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
+        if (info.getType() == TRANSIT_TASK_FRAGMENT_DRAG_RESIZE) {
+            // Jump cut for AE drag resizing because the content is veiled.
+            return new ArrayList<>();
+        }
         boolean isChangeTransition = false;
         for (TransitionInfo.Change change : info.getChanges()) {
             if (change.hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index 1f9358e..d6b9d34 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -22,6 +22,7 @@
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
 
 import static com.android.wm.shell.transition.DefaultTransitionHandler.isSupportedOverrideAnimation;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE;
 
 import static java.util.Objects.requireNonNull;
 
@@ -90,6 +91,12 @@
 
     /** Whether ActivityEmbeddingController should animate this transition. */
     public boolean shouldAnimate(@NonNull TransitionInfo info) {
+        if (info.getType() == TRANSIT_TASK_FRAGMENT_DRAG_RESIZE) {
+            // The TRANSIT_TASK_FRAGMENT_DRAG_RESIZE type happens when the user drags the
+            // interactive divider to resize the split containers. The content is veiled, so we will
+            // handle the transition with a jump cut.
+            return true;
+        }
         boolean containsEmbeddingChange = false;
         for (TransitionInfo.Change change : info.getChanges()) {
             if (!change.hasFlags(FLAG_FILLS_TASK) && change.hasFlags(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 163a896..5600664 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -646,7 +646,7 @@
 
     private void tryDispatchOnBackCancelled(IOnBackInvokedCallback callback) {
         if (!mOnBackStartDispatched) {
-            Log.e(TAG, "Skipping dispatching onBackCancelled. Start was never dispatched.");
+            Log.d(TAG, "Skipping dispatching onBackCancelled. Start was never dispatched.");
             return;
         }
         if (callback == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index 037b1ec..c988c2f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 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.
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.wm.shell.back
 
 import android.animation.Animator
@@ -34,6 +35,7 @@
 import android.view.SurfaceControl
 import android.view.animation.DecelerateInterpolator
 import android.view.animation.Interpolator
+import android.view.animation.Transformation
 import android.window.BackEvent
 import android.window.BackMotionEvent
 import android.window.BackNavigationInfo
@@ -46,52 +48,45 @@
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.animation.Interpolators
 import com.android.wm.shell.protolog.ShellProtoLogGroup
-import com.android.wm.shell.shared.annotations.ShellMainThread
-import javax.inject.Inject
 import kotlin.math.abs
 import kotlin.math.max
 import kotlin.math.min
 
-/** Class that defines cross-activity animation.  */
-@ShellMainThread
-class CrossActivityBackAnimation @Inject constructor(
+abstract class CrossActivityBackAnimation(
     private val context: Context,
     private val background: BackAnimationBackground,
-    private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+    private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+    protected val transaction: SurfaceControl.Transaction,
+    private val choreographer: Choreographer
 ) : ShellBackAnimation() {
 
-    private val startClosingRect = RectF()
-    private val targetClosingRect = RectF()
-    private val currentClosingRect = RectF()
+    protected val startClosingRect = RectF()
+    protected val targetClosingRect = RectF()
+    protected val currentClosingRect = RectF()
 
-    private val startEnteringRect = RectF()
-    private val targetEnteringRect = RectF()
-    private val currentEnteringRect = RectF()
+    protected val startEnteringRect = RectF()
+    protected val targetEnteringRect = RectF()
+    protected val currentEnteringRect = RectF()
 
-    private val backAnimRect = Rect()
+    protected val backAnimRect = Rect()
     private val cropRect = Rect()
 
     private var cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
 
-    private val backAnimationRunner = BackAnimationRunner(
-        Callback(), Runner(), context, Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY
-    )
+    private val backAnimationRunner =
+        BackAnimationRunner(Callback(), Runner(), context, Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY)
     private val initialTouchPos = PointF()
     private val transformMatrix = Matrix()
     private val tmpFloat9 = FloatArray(9)
-    private var enteringTarget: RemoteAnimationTarget? = null
-    private var closingTarget: RemoteAnimationTarget? = null
-    private val transaction = SurfaceControl.Transaction()
+    protected var enteringTarget: RemoteAnimationTarget? = null
+    protected var closingTarget: RemoteAnimationTarget? = null
     private var triggerBack = false
     private var finishCallback: IRemoteAnimationFinishedCallback? = null
     private val progressAnimator = BackProgressAnimator()
     private val displayBoundsMargin =
         context.resources.getDimension(R.dimen.cross_task_back_vertical_margin)
-    private val enteringStartOffset =
-        context.resources.getDimension(R.dimen.cross_activity_back_entering_start_offset)
 
     private val gestureInterpolator = Interpolators.BACK_GESTURE
-    private val postCommitInterpolator = Interpolators.FAST_OUT_SLOW_IN
     private val verticalMoveInterpolator: Interpolator = DecelerateInterpolator()
 
     private var scrimLayer: SurfaceControl? = null
@@ -103,13 +98,42 @@
     private var rightLetterboxLayer: SurfaceControl? = null
     private var letterboxColor: Int = 0
 
+    /** Background color to be used during the animation, also see [getBackgroundColor] */
+    protected var customizedBackgroundColor = 0
+
+    /**
+     * Whether the entering target should be shifted vertically with the user gesture in pre-commit
+     */
+    abstract val allowEnteringYShift: Boolean
+
+    /**
+     * Subclasses must set the [startEnteringRect] and [targetEnteringRect] to define the movement
+     * of the enteringTarget during pre-commit phase.
+     */
+    abstract fun preparePreCommitEnteringRectMovement()
+
+    /**
+     * Returns a base transformation to apply to the entering target during pre-commit. The system
+     * will apply the default animation on top of it.
+     */
+    protected open fun getPreCommitEnteringBaseTransformation(progress: Float): Transformation? =
+        null
+
     override fun onConfigurationChanged(newConfiguration: Configuration) {
         cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
     }
 
     override fun getRunner() = backAnimationRunner
 
-    private fun startBackAnimation(backMotionEvent: BackMotionEvent) {
+    private fun getBackgroundColor(): Int =
+        when {
+            customizedBackgroundColor != 0 -> customizedBackgroundColor
+            isLetterboxed -> letterboxColor
+            enteringTarget != null -> enteringTarget!!.taskInfo.taskDescription!!.backgroundColor
+            else -> 0
+        }
+
+    protected open fun startBackAnimation(backMotionEvent: BackMotionEvent) {
         if (enteringTarget == null || closingTarget == null) {
             ProtoLog.d(
                 ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW,
@@ -122,8 +146,8 @@
 
         transaction.setAnimationTransaction()
         isLetterboxed = closingTarget!!.taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed
-        enteringHasSameLetterbox = isLetterboxed &&
-                closingTarget!!.localBounds.equals(enteringTarget!!.localBounds)
+        enteringHasSameLetterbox =
+            isLetterboxed && closingTarget!!.localBounds.equals(enteringTarget!!.localBounds)
 
         if (isLetterboxed && !enteringHasSameLetterbox) {
             // Play animation with letterboxes, if closing and entering target have mismatching
@@ -143,32 +167,27 @@
         targetClosingRect.scaleCentered(MAX_SCALE)
         if (backMotionEvent.swipeEdge != BackEvent.EDGE_RIGHT) {
             targetClosingRect.offset(
-                startClosingRect.right - targetClosingRect.right - displayBoundsMargin, 0f
+                startClosingRect.right - targetClosingRect.right - displayBoundsMargin,
+                0f
             )
         }
 
-        // the entering target starts 96dp to the left of the screen edge...
-        startEnteringRect.set(startClosingRect)
-        startEnteringRect.offset(-enteringStartOffset, 0f)
+        preparePreCommitEnteringRectMovement()
 
-        // ...and gets scaled in sync with the closing target
-        targetEnteringRect.set(startEnteringRect)
-        targetEnteringRect.scaleCentered(MAX_SCALE)
-
-        // Draw background with task background color (or letterbox color).
-        val backgroundColor = if (isLetterboxed) {
-            letterboxColor
-        } else {
-            enteringTarget!!.taskInfo.taskDescription!!.backgroundColor
-        }
         background.ensureBackground(
-            closingTarget!!.windowConfiguration.bounds, backgroundColor, transaction
+            closingTarget!!.windowConfiguration.bounds,
+            getBackgroundColor(),
+            transaction
         )
         ensureScrimLayer()
         if (isLetterboxed && enteringHasSameLetterbox) {
             // crop left and right letterboxes
-            cropRect.set(closingTarget!!.localBounds.left, 0, closingTarget!!.localBounds.right,
-                    closingTarget!!.windowConfiguration.bounds.height())
+            cropRect.set(
+                closingTarget!!.localBounds.left,
+                0,
+                closingTarget!!.localBounds.right,
+                closingTarget!!.windowConfiguration.bounds.height()
+            )
             // and add fake letterbox square surfaces instead
             ensureLetterboxes()
         } else {
@@ -185,8 +204,14 @@
         currentClosingRect.offset(0f, yOffset)
         applyTransform(closingTarget?.leash, currentClosingRect, 1f)
         currentEnteringRect.setInterpolatedRectF(startEnteringRect, targetEnteringRect, progress)
-        currentEnteringRect.offset(0f, yOffset)
-        applyTransform(enteringTarget?.leash, currentEnteringRect, 1f)
+        if (allowEnteringYShift) currentEnteringRect.offset(0f, yOffset)
+        val enteringTransformation = getPreCommitEnteringBaseTransformation(progress)
+        applyTransform(
+            enteringTarget?.leash,
+            currentEnteringRect,
+            enteringTransformation?.alpha ?: 1f,
+            enteringTransformation
+        )
         applyTransaction()
     }
 
@@ -199,30 +224,25 @@
         val deltaYRatio = min(screenHeight / 2f, abs(rawYDelta)) / (screenHeight / 2f)
         val interpolatedYRatio: Float = verticalMoveInterpolator.getInterpolation(deltaYRatio)
         // limit y-shift so surface never passes 8dp screen margin
-        val deltaY = yDirection * interpolatedYRatio * max(
-            0f, (screenHeight - centeredRect.height()) / 2f - displayBoundsMargin
-        )
+        val deltaY =
+            max(0f, (screenHeight - centeredRect.height()) / 2f - displayBoundsMargin) *
+                interpolatedYRatio *
+                yDirection
         return deltaY
     }
 
-    private fun onGestureCommitted() {
-        if (closingTarget?.leash == null || enteringTarget?.leash == null ||
-                !enteringTarget!!.leash.isValid || !closingTarget!!.leash.isValid
+    protected open fun onGestureCommitted() {
+        if (
+            closingTarget?.leash == null ||
+                enteringTarget?.leash == null ||
+                !enteringTarget!!.leash.isValid ||
+                !closingTarget!!.leash.isValid
         ) {
             finishAnimation()
             return
         }
 
-        // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
-        // coordinate of the gesture driven phase. Let's update the start and target rects and kick
-        // off the animator
-        startClosingRect.set(currentClosingRect)
-        startEnteringRect.set(currentEnteringRect)
-        targetEnteringRect.set(backAnimRect)
-        targetClosingRect.set(backAnimRect)
-        targetClosingRect.offset(currentClosingRect.left + enteringStartOffset, 0f)
-
-        val valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION)
+        val valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(POST_COMMIT_DURATION)
         valueAnimator.addUpdateListener { animation: ValueAnimator ->
             val progress = animation.animatedFraction
             onPostCommitProgress(progress)
@@ -230,27 +250,22 @@
                 background.resetStatusBarCustomization()
             }
         }
-        valueAnimator.addListener(object : AnimatorListenerAdapter() {
-            override fun onAnimationEnd(animation: Animator) {
-                background.resetStatusBarCustomization()
-                finishAnimation()
+        valueAnimator.addListener(
+            object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator) {
+                    background.resetStatusBarCustomization()
+                    finishAnimation()
+                }
             }
-        })
+        )
         valueAnimator.start()
     }
 
-    private fun onPostCommitProgress(linearProgress: Float) {
-        val closingAlpha = max(1f - linearProgress * 2, 0f)
-        val progress = postCommitInterpolator.getInterpolation(linearProgress)
+    protected open fun onPostCommitProgress(linearProgress: Float) {
         scrimLayer?.let { transaction.setAlpha(it, maxScrimAlpha * (1f - linearProgress)) }
-        currentClosingRect.setInterpolatedRectF(startClosingRect, targetClosingRect, progress)
-        applyTransform(closingTarget?.leash, currentClosingRect, closingAlpha)
-        currentEnteringRect.setInterpolatedRectF(startEnteringRect, targetEnteringRect, progress)
-        applyTransform(enteringTarget?.leash, currentEnteringRect, 1f)
-        applyTransaction()
     }
 
-    private fun finishAnimation() {
+    protected open fun finishAnimation() {
         enteringTarget?.let {
             if (it.leash != null && it.leash.isValid) {
                 transaction.setCornerRadius(it.leash, 0f)
@@ -278,47 +293,56 @@
         enteringHasSameLetterbox = false
     }
 
-    private fun applyTransform(leash: SurfaceControl?, rect: RectF, alpha: Float) {
+    protected fun applyTransform(
+        leash: SurfaceControl?,
+        rect: RectF,
+        alpha: Float,
+        baseTransformation: Transformation? = null
+    ) {
         if (leash == null || !leash.isValid) return
         val scale = rect.width() / backAnimRect.width()
-        transformMatrix.reset()
-        val scalePivotX = if (isLetterboxed && enteringHasSameLetterbox) {
-            closingTarget!!.localBounds.left.toFloat()
-        } else {
-            0f
-        }
-        transformMatrix.setScale(scale, scale, scalePivotX, 0f)
-        transformMatrix.postTranslate(rect.left, rect.top)
-        transaction.setAlpha(leash, alpha)
-            .setMatrix(leash, transformMatrix, tmpFloat9)
+        val matrix = baseTransformation?.matrix ?: transformMatrix.apply { reset() }
+        val scalePivotX =
+            if (isLetterboxed && enteringHasSameLetterbox) {
+                closingTarget!!.localBounds.left.toFloat()
+            } else {
+                0f
+            }
+        matrix.postScale(scale, scale, scalePivotX, 0f)
+        matrix.postTranslate(rect.left, rect.top)
+        transaction
+            .setAlpha(leash, keepMinimumAlpha(alpha))
+            .setMatrix(leash, matrix, tmpFloat9)
             .setCrop(leash, cropRect)
             .setCornerRadius(leash, cornerRadius)
     }
 
-    private fun applyTransaction() {
-        transaction.setFrameTimelineVsync(Choreographer.getInstance().vsyncId)
+    protected fun applyTransaction() {
+        transaction.setFrameTimelineVsync(choreographer.vsyncId)
         transaction.apply()
     }
 
     private fun ensureScrimLayer() {
         if (scrimLayer != null) return
         val isDarkTheme: Boolean = isDarkMode(context)
-        val scrimBuilder = SurfaceControl.Builder()
-            .setName("Cross-Activity back animation scrim")
-            .setCallsite("CrossActivityBackAnimation")
-            .setColorLayer()
-            .setOpaque(false)
-            .setHidden(false)
+        val scrimBuilder =
+            SurfaceControl.Builder()
+                .setName("Cross-Activity back animation scrim")
+                .setCallsite("CrossActivityBackAnimation")
+                .setColorLayer()
+                .setOpaque(false)
+                .setHidden(false)
 
         rootTaskDisplayAreaOrganizer.attachToDisplayArea(Display.DEFAULT_DISPLAY, scrimBuilder)
         scrimLayer = scrimBuilder.build()
         val colorComponents = floatArrayOf(0f, 0f, 0f)
         maxScrimAlpha = if (isDarkTheme) MAX_SCRIM_ALPHA_DARK else MAX_SCRIM_ALPHA_LIGHT
-        val scrimCrop = if (isLetterboxed) {
-            closingTarget!!.windowConfiguration.bounds
-        } else {
-            closingTarget!!.localBounds
-        }
+        val scrimCrop =
+            if (isLetterboxed) {
+                closingTarget!!.windowConfiguration.bounds
+            } else {
+                closingTarget!!.localBounds
+            }
         transaction
             .setColor(scrimLayer, colorComponents)
             .setAlpha(scrimLayer!!, maxScrimAlpha)
@@ -339,21 +363,34 @@
     private fun ensureLetterboxes() {
         closingTarget?.let { t ->
             if (t.localBounds.left != 0 && leftLetterboxLayer == null) {
-                val bounds = Rect(0, t.windowConfiguration.bounds.top, t.localBounds.left,
-                        t.windowConfiguration.bounds.bottom)
+                val bounds =
+                    Rect(
+                        0,
+                        t.windowConfiguration.bounds.top,
+                        t.localBounds.left,
+                        t.windowConfiguration.bounds.bottom
+                    )
                 leftLetterboxLayer = ensureLetterbox(bounds)
             }
-            if (t.localBounds.right != t.windowConfiguration.bounds.right &&
-                    rightLetterboxLayer == null) {
-                val bounds = Rect(t.localBounds.right, t.windowConfiguration.bounds.top,
-                        t.windowConfiguration.bounds.right, t.windowConfiguration.bounds.bottom)
+            if (
+                t.localBounds.right != t.windowConfiguration.bounds.right &&
+                    rightLetterboxLayer == null
+            ) {
+                val bounds =
+                    Rect(
+                        t.localBounds.right,
+                        t.windowConfiguration.bounds.top,
+                        t.windowConfiguration.bounds.right,
+                        t.windowConfiguration.bounds.bottom
+                    )
                 rightLetterboxLayer = ensureLetterbox(bounds)
             }
         }
     }
 
     private fun ensureLetterbox(bounds: Rect): SurfaceControl {
-        val letterboxBuilder = SurfaceControl.Builder()
+        val letterboxBuilder =
+            SurfaceControl.Builder()
                 .setName("Cross-Activity back animation letterbox")
                 .setCallsite("CrossActivityBackAnimation")
                 .setColorLayer()
@@ -362,13 +399,17 @@
 
         rootTaskDisplayAreaOrganizer.attachToDisplayArea(Display.DEFAULT_DISPLAY, letterboxBuilder)
         val layer = letterboxBuilder.build()
-        val colorComponents = floatArrayOf(Color.red(letterboxColor) / 255f,
-                Color.green(letterboxColor) / 255f, Color.blue(letterboxColor) / 255f)
+        val colorComponents =
+            floatArrayOf(
+                Color.red(letterboxColor) / 255f,
+                Color.green(letterboxColor) / 255f,
+                Color.blue(letterboxColor) / 255f
+            )
         transaction
-                .setColor(layer, colorComponents)
-                .setCrop(layer, bounds)
-                .setRelativeLayer(layer, closingTarget!!.leash, 1)
-                .show(layer)
+            .setColor(layer, colorComponents)
+            .setCrop(layer, bounds)
+            .setRelativeLayer(layer, closingTarget!!.leash, 1)
+            .show(layer)
         return layer
     }
 
@@ -389,8 +430,8 @@
     }
 
     override fun prepareNextAnimation(
-            animationInfo: BackNavigationInfo.CustomAnimationInfo?,
-            letterboxColor: Int
+        animationInfo: BackNavigationInfo.CustomAnimationInfo?,
+        letterboxColor: Int
     ): Boolean {
         this.letterboxColor = letterboxColor
         return false
@@ -415,9 +456,7 @@
         }
 
         override fun onBackCancelled() {
-            progressAnimator.onBackCancelled {
-                finishAnimation()
-            }
+            progressAnimator.onBackCancelled { finishAnimation() }
         }
 
         override fun onBackInvoked() {
@@ -435,7 +474,8 @@
             finishedCallback: IRemoteAnimationFinishedCallback
         ) {
             ProtoLog.d(
-                ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW, "Start back to activity animation."
+                ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW,
+                "Start back to activity animation."
             )
             for (a in apps) {
                 when (a.mode) {
@@ -452,23 +492,25 @@
     }
 
     companion object {
-        /** Max scale of the entering/closing window.*/
-        private const val MAX_SCALE = 0.9f
-
-        /** Duration of post animation after gesture committed.  */
-        private const val POST_ANIMATION_DURATION = 300L
-
+        /** Max scale of the closing window. */
+        internal const val MAX_SCALE = 0.9f
         private const val MAX_SCRIM_ALPHA_DARK = 0.8f
         private const val MAX_SCRIM_ALPHA_LIGHT = 0.2f
+        private const val POST_COMMIT_DURATION = 300L
     }
 }
 
+// The target will loose focus when alpha == 0, so keep a minimum value for it.
+private fun keepMinimumAlpha(transAlpha: Float): Float {
+    return max(transAlpha.toDouble(), 0.005).toFloat()
+}
+
 private fun isDarkMode(context: Context): Boolean {
     return context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
-            Configuration.UI_MODE_NIGHT_YES
+        Configuration.UI_MODE_NIGHT_YES
 }
 
-private fun RectF.setInterpolatedRectF(start: RectF, target: RectF, progress: Float) {
+internal fun RectF.setInterpolatedRectF(start: RectF, target: RectF, progress: Float) {
     require(!(progress < 0 || progress > 1)) { "Progress value must be between 0 and 1" }
     left = start.left + (target.left - start.left) * progress
     top = start.top + (target.top - start.top) * progress
@@ -476,7 +518,7 @@
     bottom = start.bottom + (target.bottom - start.bottom) * progress
 }
 
-private fun RectF.scaleCentered(
+internal fun RectF.scaleCentered(
     scale: Float,
     pivotX: Float = left + width() / 2,
     pivotY: Float = top + height() / 2
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
new file mode 100644
index 0000000..e6ec2b4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
@@ -0,0 +1,256 @@
+/*
+ * 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.back
+
+import android.content.Context
+import android.graphics.Rect
+import android.graphics.RectF
+import android.util.MathUtils
+import android.view.Choreographer
+import android.view.SurfaceControl
+import android.view.animation.Animation
+import android.view.animation.Transformation
+import android.window.BackMotionEvent
+import android.window.BackNavigationInfo
+import com.android.internal.R
+import com.android.internal.policy.TransitionAnimation
+import com.android.internal.protolog.common.ProtoLog
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import javax.inject.Inject
+
+/** Class that handles customized predictive cross activity back animations. */
+@ShellMainThread
+class CustomCrossActivityBackAnimation(
+    context: Context,
+    background: BackAnimationBackground,
+    rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+    transaction: SurfaceControl.Transaction,
+    choreographer: Choreographer,
+    private val customAnimationLoader: CustomAnimationLoader
+) :
+    CrossActivityBackAnimation(
+        context,
+        background,
+        rootTaskDisplayAreaOrganizer,
+        transaction,
+        choreographer
+    ) {
+
+    private var enterAnimation: Animation? = null
+    private var closeAnimation: Animation? = null
+    private val transformation = Transformation()
+    private var gestureProgress = 0f
+
+    override val allowEnteringYShift = false
+
+    @Inject
+    constructor(
+        context: Context,
+        background: BackAnimationBackground,
+        rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+    ) : this(
+        context,
+        background,
+        rootTaskDisplayAreaOrganizer,
+        SurfaceControl.Transaction(),
+        Choreographer.getInstance(),
+        CustomAnimationLoader(
+            TransitionAnimation(context, false /* debug */, "CustomCrossActivityBackAnimation")
+        )
+    )
+
+    override fun preparePreCommitEnteringRectMovement() {
+        // No movement for the entering rect
+        startEnteringRect.set(startClosingRect)
+        targetEnteringRect.set(startClosingRect)
+    }
+
+    override fun getPreCommitEnteringBaseTransformation(progress: Float): Transformation {
+        gestureProgress = progress
+        transformation.clear()
+        enterAnimation!!.getTransformationAt(progress * PRE_COMMIT_MAX_PROGRESS, transformation)
+        return transformation
+    }
+
+    override fun startBackAnimation(backMotionEvent: BackMotionEvent) {
+        super.startBackAnimation(backMotionEvent)
+        if (
+            closeAnimation == null ||
+                enterAnimation == null ||
+                closingTarget == null ||
+                enteringTarget == null
+        ) {
+            ProtoLog.d(
+                ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW,
+                "Enter animation or close animation is null."
+            )
+            return
+        }
+        initializeAnimation(closeAnimation!!, closingTarget!!.localBounds)
+        initializeAnimation(enterAnimation!!, enteringTarget!!.localBounds)
+    }
+
+    override fun onPostCommitProgress(linearProgress: Float) {
+        super.onPostCommitProgress(linearProgress)
+        if (closingTarget == null || enteringTarget == null) return
+
+        // TODO: Should we use the duration from the custom xml spec for the post-commit animation?
+        applyTransform(closingTarget!!.leash, currentClosingRect, linearProgress, closeAnimation!!)
+        val enteringProgress =
+            MathUtils.lerp(gestureProgress * PRE_COMMIT_MAX_PROGRESS, 1f, linearProgress)
+        applyTransform(
+            enteringTarget!!.leash,
+            currentEnteringRect,
+            enteringProgress,
+            enterAnimation!!
+        )
+        applyTransaction()
+    }
+
+    private fun applyTransform(
+        leash: SurfaceControl,
+        rect: RectF,
+        progress: Float,
+        animation: Animation
+    ) {
+        transformation.clear()
+        animation.getTransformationAt(progress, transformation)
+        applyTransform(leash, rect, transformation.alpha, transformation)
+    }
+
+    override fun finishAnimation() {
+        closeAnimation?.reset()
+        closeAnimation = null
+        enterAnimation?.reset()
+        enterAnimation = null
+        transformation.clear()
+        gestureProgress = 0f
+        super.finishAnimation()
+    }
+
+    /** Load customize animation before animation start. */
+    override fun prepareNextAnimation(
+        animationInfo: BackNavigationInfo.CustomAnimationInfo?,
+        letterboxColor: Int
+    ): Boolean {
+        super.prepareNextAnimation(animationInfo, letterboxColor)
+        if (animationInfo == null) return false
+        customAnimationLoader.loadAll(animationInfo)?.let { result ->
+            closeAnimation = result.closeAnimation
+            enterAnimation = result.enterAnimation
+            customizedBackgroundColor = result.backgroundColor
+            return true
+        }
+        return false
+    }
+
+    class AnimationLoadResult {
+        var closeAnimation: Animation? = null
+        var enterAnimation: Animation? = null
+        var backgroundColor = 0
+    }
+
+    companion object {
+        private const val PRE_COMMIT_MAX_PROGRESS = 0.2f
+    }
+}
+
+/** Helper class to load custom animation. */
+class CustomAnimationLoader(private val transitionAnimation: TransitionAnimation) {
+
+    /**
+     * Load both enter and exit animation for the close activity transition. Note that the result is
+     * only valid if the exit animation has set and loaded success. If the entering animation has
+     * not set(i.e. 0), here will load the default entering animation for it.
+     *
+     * @param animationInfo The information of customize animation, which can be set from
+     *   [Activity.overrideActivityTransition] and/or [LayoutParams.windowAnimations]
+     */
+    fun loadAll(
+        animationInfo: BackNavigationInfo.CustomAnimationInfo
+    ): CustomCrossActivityBackAnimation.AnimationLoadResult? {
+        if (animationInfo.packageName.isEmpty()) return null
+        val close = loadAnimation(animationInfo, false) ?: return null
+        val open = loadAnimation(animationInfo, true)
+        val result = CustomCrossActivityBackAnimation.AnimationLoadResult()
+        result.closeAnimation = close
+        result.enterAnimation = open
+        result.backgroundColor = animationInfo.customBackground
+        return result
+    }
+
+    /**
+     * Load enter or exit animation from CustomAnimationInfo
+     *
+     * @param animationInfo The information for customize animation.
+     * @param enterAnimation true when load for enter animation, false for exit animation.
+     * @return Loaded animation.
+     */
+    fun loadAnimation(
+        animationInfo: BackNavigationInfo.CustomAnimationInfo,
+        enterAnimation: Boolean
+    ): Animation? {
+        var a: Animation? = null
+        // Activity#overrideActivityTransition has higher priority than windowAnimations
+        // Try to get animation from Activity#overrideActivityTransition
+        if (
+            enterAnimation && animationInfo.customEnterAnim != 0 ||
+                !enterAnimation && animationInfo.customExitAnim != 0
+        ) {
+            a =
+                transitionAnimation.loadAppTransitionAnimation(
+                    animationInfo.packageName,
+                    if (enterAnimation) animationInfo.customEnterAnim
+                    else animationInfo.customExitAnim
+                )
+        } else if (animationInfo.windowAnimations != 0) {
+            // try to get animation from LayoutParams#windowAnimations
+            a =
+                transitionAnimation.loadAnimationAttr(
+                    animationInfo.packageName,
+                    animationInfo.windowAnimations,
+                    if (enterAnimation) R.styleable.WindowAnimation_activityCloseEnterAnimation
+                    else R.styleable.WindowAnimation_activityCloseExitAnimation,
+                    false /* translucent */
+                )
+        }
+        // Only allow to load default animation for opening target.
+        if (a == null && enterAnimation) {
+            a = loadDefaultOpenAnimation()
+        }
+        if (a != null) {
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW, "custom animation loaded %s", a)
+        } else {
+            ProtoLog.e(ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW, "No custom animation loaded")
+        }
+        return a
+    }
+
+    private fun loadDefaultOpenAnimation(): Animation? {
+        return transitionAnimation.loadDefaultAnimationAttr(
+            R.styleable.WindowAnimation_activityCloseEnterAnimation,
+            false /* translucent */
+        )
+    }
+}
+
+private fun initializeAnimation(animation: Animation, bounds: Rect) {
+    val width = bounds.width()
+    val height = bounds.height()
+    animation.initialize(width, height, width, height)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
deleted file mode 100644
index e27b40e..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
+++ /dev/null
@@ -1,443 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.back;
-
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
-import static android.view.RemoteAnimationTarget.MODE_OPENING;
-
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.util.FloatProperty;
-import android.view.Choreographer;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.WindowManager.LayoutParams;
-import android.view.animation.Animation;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Transformation;
-import android.window.BackEvent;
-import android.window.BackMotionEvent;
-import android.window.BackNavigationInfo;
-import android.window.BackProgressAnimator;
-import android.window.IOnBackInvokedCallback;
-
-import com.android.internal.R;
-import com.android.internal.dynamicanimation.animation.SpringAnimation;
-import com.android.internal.dynamicanimation.animation.SpringForce;
-import com.android.internal.policy.ScreenDecorationsUtils;
-import com.android.internal.policy.TransitionAnimation;
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.shared.annotations.ShellMainThread;
-
-import javax.inject.Inject;
-
-/** Class that handle customized close activity transition animation. */
-@ShellMainThread
-public class CustomizeActivityAnimation extends ShellBackAnimation {
-    private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
-    private final BackAnimationRunner mBackAnimationRunner;
-    private float mCornerRadius;
-    private final SurfaceControl.Transaction mTransaction;
-    private final BackAnimationBackground mBackground;
-    private RemoteAnimationTarget mEnteringTarget;
-    private RemoteAnimationTarget mClosingTarget;
-    private IRemoteAnimationFinishedCallback mFinishCallback;
-    /** Duration of post animation after gesture committed. */
-    private static final int POST_ANIMATION_DURATION = 250;
-
-    private static final int SCALE_FACTOR = 1000;
-    private final SpringAnimation mProgressSpring;
-    private float mLatestProgress = 0.0f;
-
-    private static final float TARGET_COMMIT_PROGRESS = 0.5f;
-
-    private final float[] mTmpFloat9 = new float[9];
-    private final DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator();
-
-    final CustomAnimationLoader mCustomAnimationLoader;
-    private Animation mEnterAnimation;
-    private Animation mCloseAnimation;
-    private int mNextBackgroundColor;
-    final Transformation mTransformation = new Transformation();
-
-    private final Choreographer mChoreographer;
-    private final Context mContext;
-
-    @Inject
-    public CustomizeActivityAnimation(Context context, BackAnimationBackground background) {
-        this(context, background, new SurfaceControl.Transaction(), null);
-    }
-
-    CustomizeActivityAnimation(Context context, BackAnimationBackground background,
-            SurfaceControl.Transaction transaction, Choreographer choreographer) {
-        mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
-        mBackground = background;
-        mBackAnimationRunner = new BackAnimationRunner(
-                new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY);
-        mCustomAnimationLoader = new CustomAnimationLoader(context);
-
-        mProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
-        mProgressSpring.setSpring(new SpringForce()
-                .setStiffness(SpringForce.STIFFNESS_MEDIUM)
-                .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
-        mTransaction = transaction == null ? new SurfaceControl.Transaction() : transaction;
-        mChoreographer = choreographer != null ? choreographer : Choreographer.getInstance();
-        mContext = context;
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext);
-    }
-
-    private float getLatestProgress() {
-        return mLatestProgress * SCALE_FACTOR;
-    }
-    private void setLatestProgress(float value) {
-        mLatestProgress = value / SCALE_FACTOR;
-        applyTransformTransaction(mLatestProgress);
-    }
-
-    private static final FloatProperty<CustomizeActivityAnimation> ENTER_PROGRESS_PROP =
-            new FloatProperty<>("enter") {
-                @Override
-                public void setValue(CustomizeActivityAnimation anim, float value) {
-                    anim.setLatestProgress(value);
-                }
-
-                @Override
-                public Float get(CustomizeActivityAnimation object) {
-                    return object.getLatestProgress();
-                }
-            };
-
-    // The target will lose focus when alpha == 0, so keep a minimum value for it.
-    private static float keepMinimumAlpha(float transAlpha) {
-        return Math.max(transAlpha, 0.005f);
-    }
-
-    private static void initializeAnimation(Animation animation, Rect bounds) {
-        final int width = bounds.width();
-        final int height = bounds.height();
-        animation.initialize(width, height, width, height);
-    }
-
-    private void startBackAnimation() {
-        if (mEnteringTarget == null || mClosingTarget == null
-                || mCloseAnimation == null || mEnterAnimation == null) {
-            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
-            return;
-        }
-        initializeAnimation(mCloseAnimation, mClosingTarget.localBounds);
-        initializeAnimation(mEnterAnimation, mEnteringTarget.localBounds);
-
-        // Draw background with task background color.
-        if (mEnteringTarget.taskInfo != null && mEnteringTarget.taskInfo.taskDescription != null) {
-            mBackground.ensureBackground(mClosingTarget.windowConfiguration.getBounds(),
-                    mNextBackgroundColor == Color.TRANSPARENT
-                            ? mEnteringTarget.taskInfo.taskDescription.getBackgroundColor()
-                            : mNextBackgroundColor,
-                    mTransaction);
-        }
-    }
-
-    private void applyTransformTransaction(float progress) {
-        if (mClosingTarget == null || mEnteringTarget == null) {
-            return;
-        }
-        applyTransform(mClosingTarget.leash, progress, mCloseAnimation);
-        applyTransform(mEnteringTarget.leash, progress, mEnterAnimation);
-        mTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId());
-        mTransaction.apply();
-    }
-
-    private void applyTransform(SurfaceControl leash, float progress, Animation animation) {
-        mTransformation.clear();
-        animation.getTransformationAt(progress, mTransformation);
-        mTransaction.setMatrix(leash, mTransformation.getMatrix(), mTmpFloat9);
-        mTransaction.setAlpha(leash, keepMinimumAlpha(mTransformation.getAlpha()));
-        mTransaction.setCornerRadius(leash, mCornerRadius);
-    }
-
-    void finishAnimation() {
-        if (mCloseAnimation != null) {
-            mCloseAnimation.reset();
-            mCloseAnimation = null;
-        }
-        if (mEnterAnimation != null) {
-            mEnterAnimation.reset();
-            mEnterAnimation = null;
-        }
-        if (mEnteringTarget != null) {
-            mEnteringTarget.leash.release();
-            mEnteringTarget = null;
-        }
-        if (mClosingTarget != null) {
-            mClosingTarget.leash.release();
-            mClosingTarget = null;
-        }
-        if (mBackground != null) {
-            mBackground.removeBackground(mTransaction);
-        }
-        mTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId());
-        mTransaction.apply();
-        mTransformation.clear();
-        mLatestProgress = 0;
-        mNextBackgroundColor = Color.TRANSPARENT;
-        if (mFinishCallback != null) {
-            try {
-                mFinishCallback.onAnimationFinished();
-            } catch (RemoteException e) {
-                e.printStackTrace();
-            }
-            mFinishCallback = null;
-        }
-        mProgressSpring.animateToFinalPosition(0);
-        mProgressSpring.skipToEnd();
-    }
-
-    void onGestureProgress(@NonNull BackEvent backEvent) {
-        if (mEnteringTarget == null || mClosingTarget == null
-                || mCloseAnimation == null || mEnterAnimation == null) {
-            return;
-        }
-
-        final float progress = backEvent.getProgress();
-
-        float springProgress = (progress > 0.1f
-                ? mapLinear(progress, 0.1f, 1f, TARGET_COMMIT_PROGRESS, 1f)
-                : mapLinear(progress, 0, 1f, 0f, TARGET_COMMIT_PROGRESS)) * SCALE_FACTOR;
-
-        mProgressSpring.animateToFinalPosition(springProgress);
-    }
-
-    static float mapLinear(float x, float a1, float a2, float b1, float b2) {
-        return b1 + (x - a1) * (b2 - b1) / (a2 - a1);
-    }
-
-    void onGestureCommitted() {
-        if (mEnteringTarget == null || mClosingTarget == null
-                || mCloseAnimation == null || mEnterAnimation == null) {
-            finishAnimation();
-            return;
-        }
-        mProgressSpring.cancel();
-
-        // Enter phase 2 of the animation
-        final ValueAnimator valueAnimator = ValueAnimator.ofFloat(mLatestProgress, 1f)
-                .setDuration(POST_ANIMATION_DURATION);
-        valueAnimator.setInterpolator(mDecelerateInterpolator);
-        valueAnimator.addUpdateListener(animation -> {
-            float progress = (float) animation.getAnimatedValue();
-            applyTransformTransaction(progress);
-        });
-
-        valueAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                finishAnimation();
-            }
-        });
-        valueAnimator.start();
-    }
-
-    /** Load customize animation before animation start. */
-    @Override
-    public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo,
-            int letterboxColor) {
-        if (animationInfo == null) {
-            return false;
-        }
-        final AnimationLoadResult result = mCustomAnimationLoader.loadAll(animationInfo);
-        if (result != null) {
-            mCloseAnimation = result.mCloseAnimation;
-            mEnterAnimation = result.mEnterAnimation;
-            mNextBackgroundColor = result.mBackgroundColor;
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public BackAnimationRunner getRunner() {
-        return mBackAnimationRunner;
-    }
-
-    private final class Callback extends IOnBackInvokedCallback.Default {
-        @Override
-        public void onBackStarted(BackMotionEvent backEvent) {
-            // in case we're still animating an onBackCancelled event, let's remove the finish-
-            // callback from the progress animator to prevent calling finishAnimation() before
-            // restarting a new animation
-            mProgressAnimator.removeOnBackCancelledFinishCallback();
-
-            mProgressAnimator.onBackStarted(backEvent,
-                    CustomizeActivityAnimation.this::onGestureProgress);
-        }
-
-        @Override
-        public void onBackProgressed(@NonNull BackMotionEvent backEvent) {
-            mProgressAnimator.onBackProgressed(backEvent);
-        }
-
-        @Override
-        public void onBackCancelled() {
-            mProgressAnimator.onBackCancelled(CustomizeActivityAnimation.this::finishAnimation);
-        }
-
-        @Override
-        public void onBackInvoked() {
-            mProgressAnimator.reset();
-            onGestureCommitted();
-        }
-    }
-
-    private final class Runner extends IRemoteAnimationRunner.Default {
-        @Override
-        public void onAnimationStart(
-                int transit,
-                RemoteAnimationTarget[] apps,
-                RemoteAnimationTarget[] wallpapers,
-                RemoteAnimationTarget[] nonApps,
-                IRemoteAnimationFinishedCallback finishedCallback) {
-            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to customize animation.");
-            for (RemoteAnimationTarget a : apps) {
-                if (a.mode == MODE_CLOSING) {
-                    mClosingTarget = a;
-                }
-                if (a.mode == MODE_OPENING) {
-                    mEnteringTarget = a;
-                }
-            }
-            if (mCloseAnimation == null || mEnterAnimation == null) {
-                ProtoLog.d(WM_SHELL_BACK_PREVIEW,
-                        "No animation loaded, should choose cross-activity animation?");
-            }
-
-            startBackAnimation();
-            mFinishCallback = finishedCallback;
-        }
-
-        @Override
-        public void onAnimationCancelled() {
-            finishAnimation();
-        }
-    }
-
-
-    static final class AnimationLoadResult {
-        Animation mCloseAnimation;
-        Animation mEnterAnimation;
-        int mBackgroundColor;
-    }
-
-    /**
-     * Helper class to load custom animation.
-     */
-    static class CustomAnimationLoader {
-        final TransitionAnimation mTransitionAnimation;
-
-        CustomAnimationLoader(Context context) {
-            mTransitionAnimation = new TransitionAnimation(
-                    context, false /* debug */, "CustomizeBackAnimation");
-        }
-
-        /**
-         * Load both enter and exit animation for the close activity transition.
-         * Note that the result is only valid if the exit animation has set and loaded success.
-         * If the entering animation has not set(i.e. 0), here will load the default entering
-         * animation for it.
-         *
-         * @param animationInfo The information of customize animation, which can be set from
-         * {@link Activity#overrideActivityTransition} and/or
-         * {@link LayoutParams#windowAnimations}
-         */
-        AnimationLoadResult loadAll(BackNavigationInfo.CustomAnimationInfo animationInfo) {
-            if (animationInfo.getPackageName().isEmpty()) {
-                return null;
-            }
-            final Animation close = loadAnimation(animationInfo, false);
-            if (close == null) {
-                return null;
-            }
-            final Animation open = loadAnimation(animationInfo, true);
-            AnimationLoadResult result = new AnimationLoadResult();
-            result.mCloseAnimation = close;
-            result.mEnterAnimation = open;
-            result.mBackgroundColor = animationInfo.getCustomBackground();
-            return result;
-        }
-
-        /**
-         * Load enter or exit animation from CustomAnimationInfo
-         * @param animationInfo The information for customize animation.
-         * @param enterAnimation true when load for enter animation, false for exit animation.
-         * @return Loaded animation.
-         */
-        @Nullable
-        Animation loadAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo,
-                boolean enterAnimation) {
-            Animation a = null;
-            // Activity#overrideActivityTransition has higher priority than windowAnimations
-            // Try to get animation from Activity#overrideActivityTransition
-            if ((enterAnimation && animationInfo.getCustomEnterAnim() != 0)
-                    || (!enterAnimation && animationInfo.getCustomExitAnim() != 0)) {
-                a = mTransitionAnimation.loadAppTransitionAnimation(
-                        animationInfo.getPackageName(),
-                        enterAnimation ? animationInfo.getCustomEnterAnim()
-                                : animationInfo.getCustomExitAnim());
-            } else if (animationInfo.getWindowAnimations() != 0) {
-                // try to get animation from LayoutParams#windowAnimations
-                a = mTransitionAnimation.loadAnimationAttr(animationInfo.getPackageName(),
-                        animationInfo.getWindowAnimations(), enterAnimation
-                                ? R.styleable.WindowAnimation_activityCloseEnterAnimation
-                                : R.styleable.WindowAnimation_activityCloseExitAnimation,
-                        false /* translucent */);
-            }
-            // Only allow to load default animation for opening target.
-            if (a == null && enterAnimation) {
-                a = loadDefaultOpenAnimation();
-            }
-            if (a != null) {
-                ProtoLog.d(WM_SHELL_BACK_PREVIEW, "custom animation loaded %s", a);
-            } else {
-                ProtoLog.e(WM_SHELL_BACK_PREVIEW, "No custom animation loaded");
-            }
-            return a;
-        }
-
-        private Animation loadDefaultOpenAnimation() {
-            return mTransitionAnimation.loadDefaultAnimationAttr(
-                    R.styleable.WindowAnimation_activityCloseEnterAnimation,
-                    false /* translucent */);
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
new file mode 100644
index 0000000..f33c5b9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.back
+
+import android.content.Context
+import android.view.Choreographer
+import android.view.SurfaceControl
+import com.android.wm.shell.R
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.animation.Interpolators
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import javax.inject.Inject
+import kotlin.math.max
+
+/** Class that defines cross-activity animation. */
+@ShellMainThread
+class DefaultCrossActivityBackAnimation
+@Inject
+constructor(
+    context: Context,
+    background: BackAnimationBackground,
+    rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+) :
+    CrossActivityBackAnimation(
+        context,
+        background,
+        rootTaskDisplayAreaOrganizer,
+        SurfaceControl.Transaction(),
+        Choreographer.getInstance()
+    ) {
+
+    private val postCommitInterpolator = Interpolators.FAST_OUT_SLOW_IN
+    private val enteringStartOffset =
+        context.resources.getDimension(R.dimen.cross_activity_back_entering_start_offset)
+    override val allowEnteringYShift = true
+
+    override fun preparePreCommitEnteringRectMovement() {
+        // the entering target starts 96dp to the left of the screen edge...
+        startEnteringRect.set(startClosingRect)
+        startEnteringRect.offset(-enteringStartOffset, 0f)
+        // ...and gets scaled in sync with the closing target
+        targetEnteringRect.set(startEnteringRect)
+        targetEnteringRect.scaleCentered(MAX_SCALE)
+    }
+
+    override fun onGestureCommitted() {
+        // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
+        // coordinate of the gesture driven phase. Let's update the start and target rects and kick
+        // off the animator in the superclass
+        startClosingRect.set(currentClosingRect)
+        startEnteringRect.set(currentEnteringRect)
+        targetEnteringRect.set(backAnimRect)
+        targetClosingRect.set(backAnimRect)
+        targetClosingRect.offset(currentClosingRect.left + enteringStartOffset, 0f)
+        super.onGestureCommitted()
+    }
+
+    override fun onPostCommitProgress(linearProgress: Float) {
+        super.onPostCommitProgress(linearProgress)
+        val closingAlpha = max(1f - linearProgress * 2, 0f)
+        val progress = postCommitInterpolator.getInterpolation(linearProgress)
+        currentClosingRect.setInterpolatedRectF(startClosingRect, targetClosingRect, progress)
+        applyTransform(closingTarget?.leash, currentClosingRect, closingAlpha)
+        currentEnteringRect.setInterpolatedRectF(startEnteringRect, targetEnteringRect, progress)
+        applyTransform(enteringTarget?.leash, currentEnteringRect, 1f)
+        applyTransaction()
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 037397c..38c3443 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -87,6 +87,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.launcher3.icons.BubbleIconFactory;
+import com.android.wm.shell.Flags;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
@@ -170,6 +171,8 @@
          * the pointer might need to be updated.
          */
         void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer);
+        /** Called when the bubble overflow empty state changes, used to show/hide the overflow. */
+        void bubbleOverflowChanged(boolean hasBubbles);
     }
 
     private final Context mContext;
@@ -1168,27 +1171,44 @@
      * @param bubbleKey key of the bubble being dragged
      */
     public void startBubbleDrag(String bubbleKey) {
-        onBubbleDrag(bubbleKey, true /* isBeingDragged */);
+        if (mBubbleData.getSelectedBubble() != null) {
+            mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ false);
+        }
+        if (mBubbleStateListener != null) {
+            boolean overflow = BubbleOverflow.KEY.equals(bubbleKey);
+            Rect rect = new Rect();
+            mBubblePositioner.getBubbleBarExpandedViewBounds(mBubblePositioner.isBubbleBarOnLeft(),
+                    overflow, rect);
+            BubbleBarUpdate update = new BubbleBarUpdate();
+            update.expandedViewDropTargetSize = new Point(rect.width(), rect.height());
+            mBubbleStateListener.onBubbleStateChange(update);
+        }
     }
 
     /**
-     * A bubble is no longer being dragged in Launcher. As was released in given location.
+     * A bubble is no longer being dragged in Launcher. And was released in given location.
      * Will be called only when bubble bar is expanded.
      *
-     * @param bubbleKey key of the bubble being dragged
      * @param location  location where bubble was released
      */
-    public void stopBubbleDrag(String bubbleKey, BubbleBarLocation location) {
+    public void stopBubbleDrag(BubbleBarLocation location) {
         mBubblePositioner.setBubbleBarLocation(location);
-        onBubbleDrag(bubbleKey, false /* isBeingDragged */);
+        if (mBubbleData.getSelectedBubble() != null) {
+            mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true);
+        }
     }
 
-    private void onBubbleDrag(String bubbleKey, boolean isBeingDragged) {
-        // TODO(b/330585402): collapse stack if any bubble is dragged
-        if (mBubbleData.getSelectedBubble() != null
-                && mBubbleData.getSelectedBubble().getKey().equals(bubbleKey)) {
-            // Should collapse/expand only if equals to selected bubble.
-            mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ !isBeingDragged);
+    /**
+     * A bubble was dragged and is released in dismiss target in Launcher.
+     *
+     * @param bubbleKey key of the bubble being dragged to dismiss target
+     */
+    public void dragBubbleToDismiss(String bubbleKey) {
+        String selectedBubbleKey = mBubbleData.getSelectedBubbleKey();
+        removeBubble(bubbleKey, Bubbles.DISMISS_USER_GESTURE);
+        if (selectedBubbleKey != null && !selectedBubbleKey.equals(bubbleKey)) {
+            // We did not remove the selected bubble. Expand it again
+            mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true);
         }
     }
 
@@ -1368,28 +1388,32 @@
         }
 
         String appBubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(), user);
-        Log.i(TAG, "showOrHideAppBubble, key= " + appBubbleKey + " stackVisibility= "
-                + (mStackView != null ? mStackView.getVisibility() : " null ")
-                + " statusBarShade=" + mIsStatusBarShade);
         PackageManager packageManager = getPackageManagerForUser(mContext, user.getIdentifier());
-        if (!isResizableActivity(intent, packageManager, appBubbleKey)) return;
+        if (!isResizableActivity(intent, packageManager, appBubbleKey)) return; // logs errors
 
         Bubble existingAppBubble = mBubbleData.getBubbleInStackWithKey(appBubbleKey);
+        ProtoLog.d(WM_SHELL_BUBBLES,
+                "showOrHideAppBubble, key=%s existingAppBubble=%s stackVisibility=%s "
+                        + "statusBarShade=%s",
+                appBubbleKey, existingAppBubble,
+                (mStackView != null ? mStackView.getVisibility() : "null"),
+                mIsStatusBarShade);
+
         if (existingAppBubble != null) {
             BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
             if (isStackExpanded()) {
                 if (selectedBubble != null && appBubbleKey.equals(selectedBubble.getKey())) {
+                    ProtoLog.d(WM_SHELL_BUBBLES, "collapseStack for %s", appBubbleKey);
                     // App bubble is expanded, lets collapse
-                    Log.i(TAG, "  showOrHideAppBubble, selected bubble is app bubble, collapsing");
                     collapseStack();
                 } else {
+                    ProtoLog.d(WM_SHELL_BUBBLES, "setSelected for %s", appBubbleKey);
                     // App bubble is not selected, select it
-                    Log.i(TAG, "  showOrHideAppBubble, expanded, selecting existing app bubble");
                     mBubbleData.setSelectedBubble(existingAppBubble);
                 }
             } else {
+                ProtoLog.d(WM_SHELL_BUBBLES, "setSelectedBubbleAndExpandStack %s", appBubbleKey);
                 // App bubble is not selected, select it & expand
-                Log.i(TAG, "  showOrHideAppBubble, expand and select existing app bubble");
                 mBubbleData.setSelectedBubbleAndExpandStack(existingAppBubble);
             }
         } else {
@@ -1397,13 +1421,12 @@
             Bubble b = mBubbleData.getOverflowBubbleWithKey(appBubbleKey);
             if (b != null) {
                 // It's in the overflow, so remove it & reinflate
-                Log.i(TAG, "  showOrHideAppBubble, expanding app bubble from overflow");
-                mBubbleData.removeOverflowBubble(b);
+                mBubbleData.dismissBubbleWithKey(appBubbleKey, Bubbles.DISMISS_NOTIF_CANCEL);
             } else {
                 // App bubble does not exist, lets add and expand it
-                Log.i(TAG, "  showOrHideAppBubble, creating and expanding app bubble");
                 b = Bubble.createAppBubble(intent, user, icon, mMainExecutor);
             }
+            ProtoLog.d(WM_SHELL_BUBBLES, "inflateAndAdd %s", appBubbleKey);
             b.setShouldAutoExpand(true);
             inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
         }
@@ -1841,6 +1864,15 @@
             }
 
         }
+
+        @Override
+        public void bubbleOverflowChanged(boolean hasBubbles) {
+            if (Flags.enableOptionalBubbleOverflow()) {
+                if (mStackView != null) {
+                    mStackView.showOverflow(hasBubbles);
+                }
+            }
+        }
     };
 
     /** When bubbles are in the bubble bar, this will be used to notify bubble bar views. */
@@ -1873,6 +1905,11 @@
         }
 
         @Override
+        public void bubbleOverflowChanged(boolean hasBubbles) {
+            // Nothing to do for our views, handled by launcher / in the bubble bar.
+        }
+
+        @Override
         public void suppressionChanged(Bubble bubble, boolean isSuppressed) {
             if (mLayerView != null) {
                 // TODO (b/273316505) handle suppression changes, although might not need to
@@ -1911,7 +1948,7 @@
             ProtoLog.d(WM_SHELL_BUBBLES, "mBubbleDataListener#applyUpdate:"
                     + " added=%s removed=%b updated=%s orderChanged=%b expansionChanged=%b"
                     + " expanded=%b selectionChanged=%b selected=%s"
-                    + " suppressed=%s unsupressed=%s shouldShowEducation=%b",
+                    + " suppressed=%s unsupressed=%s shouldShowEducation=%b showOverflowChanged=%b",
                     update.addedBubble != null ? update.addedBubble.getKey() : "null",
                     !update.removedBubbles.isEmpty(),
                     update.updatedBubble != null ? update.updatedBubble.getKey() : "null",
@@ -1920,13 +1957,17 @@
                     update.selectedBubble != null ? update.selectedBubble.getKey() : "null",
                     update.suppressedBubble != null ? update.suppressedBubble.getKey() : "null",
                     update.unsuppressedBubble != null ? update.unsuppressedBubble.getKey() : "null",
-                    update.shouldShowEducation);
+                    update.shouldShowEducation, update.showOverflowChanged);
 
             ensureBubbleViewsAndWindowCreated();
 
             // Lazy load overflow bubbles from disk
             loadOverflowBubblesFromDisk();
 
+            if (update.showOverflowChanged) {
+                mBubbleViewCallback.bubbleOverflowChanged(!update.overflowBubbles.isEmpty());
+            }
+
             // If bubbles in the overflow have a dot, make sure the overflow shows a dot
             updateOverflowButtonDot();
 
@@ -2330,12 +2371,6 @@
         }
 
         @Override
-        public void removeBubble(String key) {
-            mMainExecutor.execute(
-                    () -> mController.removeBubble(key, Bubbles.DISMISS_USER_GESTURE));
-        }
-
-        @Override
         public void removeAllBubbles() {
             mMainExecutor.execute(() -> mController.removeAllBubbles(Bubbles.DISMISS_USER_GESTURE));
         }
@@ -2351,8 +2386,13 @@
         }
 
         @Override
-        public void stopBubbleDrag(String bubbleKey, BubbleBarLocation location) {
-            mMainExecutor.execute(() -> mController.stopBubbleDrag(bubbleKey, location));
+        public void stopBubbleDrag(BubbleBarLocation location) {
+            mMainExecutor.execute(() -> mController.stopBubbleDrag(location));
+        }
+
+        @Override
+        public void dragBubbleToDismiss(String key) {
+            mMainExecutor.execute(() -> mController.dragBubbleToDismiss(key));
         }
 
         @Override
@@ -2369,7 +2409,10 @@
 
         @Override
         public void setBubbleBarBounds(Rect bubbleBarBounds) {
-            mMainExecutor.execute(() -> mBubblePositioner.setBubbleBarBounds(bubbleBarBounds));
+            mMainExecutor.execute(() -> {
+                mBubblePositioner.setBubbleBarBounds(bubbleBarBounds);
+                if (mLayerView != null) mLayerView.updateExpandedView();
+            });
         }
     }
 
@@ -2681,6 +2724,15 @@
                     () -> BubbleController.this.onSensitiveNotificationProtectionStateChanged(
                             sensitiveNotificationProtectionActive));
         }
+
+        @Override
+        public boolean canShowBubbleNotification() {
+            // in bubble bar mode, when the IME is visible we can't animate new bubbles.
+            if (BubbleController.this.isShowingAsBubbleBar()) {
+                return !BubbleController.this.mBubblePositioner.getIsImeVisible();
+            }
+            return true;
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index ae3d0c5..26483c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -77,6 +77,7 @@
         boolean suppressedSummaryChanged;
         boolean expanded;
         boolean shouldShowEducation;
+        boolean showOverflowChanged;
         @Nullable BubbleViewProvider selectedBubble;
         @Nullable Bubble addedBubble;
         @Nullable Bubble updatedBubble;
@@ -109,7 +110,8 @@
                     || suppressedBubble != null
                     || unsuppressedBubble != null
                     || suppressedSummaryChanged
-                    || suppressedSummaryGroup != null;
+                    || suppressedSummaryGroup != null
+                    || showOverflowChanged;
         }
 
         void bubbleRemoved(Bubble bubbleToRemove, @DismissReason int reason) {
@@ -157,6 +159,8 @@
                     bubbleBarUpdate.bubbleKeysInOrder.add(bubbles.get(i).getKey());
                 }
             }
+            bubbleBarUpdate.showOverflowChanged = showOverflowChanged;
+            bubbleBarUpdate.showOverflow = !overflowBubbles.isEmpty();
             return bubbleBarUpdate;
         }
 
@@ -323,6 +327,14 @@
         return mSelectedBubble;
     }
 
+    /**
+     * Returns the key of the selected bubble, or null if no bubble is selected.
+     */
+    @Nullable
+    public String getSelectedBubbleKey() {
+        return mSelectedBubble != null ? mSelectedBubble.getKey() : null;
+    }
+
     public BubbleOverflow getOverflow() {
         return mOverflow;
     }
@@ -410,6 +422,9 @@
             if (bubbleToReturn != null) {
                 // Promoting from overflow
                 mOverflowBubbles.remove(bubbleToReturn);
+                if (mOverflowBubbles.isEmpty()) {
+                    mStateChange.showOverflowChanged = true;
+                }
             } else if (mPendingBubbles.containsKey(key)) {
                 // Update while it was pending
                 bubbleToReturn = mPendingBubbles.get(key);
@@ -497,19 +512,6 @@
     }
 
     /**
-     * Explicitly removes a bubble from the overflow, if it exists.
-     *
-     * @param bubble the bubble to remove.
-     */
-    public void removeOverflowBubble(Bubble bubble) {
-        if (bubble == null) return;
-        if (mOverflowBubbles.remove(bubble)) {
-            mStateChange.removedOverflowBubble = bubble;
-            dispatchPendingChanges();
-        }
-    }
-
-    /**
      * Adds a group key indicating that the summary for this group should be suppressed.
      *
      * @param groupKey the group key of the group whose summary should be suppressed.
@@ -683,7 +685,6 @@
         if (indexToRemove == -1) {
             if (hasOverflowBubbleWithKey(key)
                     && shouldRemoveHiddenBubble) {
-
                 Bubble b = getOverflowBubbleWithKey(key);
                 ProtoLog.d(WM_SHELL_BUBBLES, "doRemove - cancel overflow bubble=%s", key);
                 if (b != null) {
@@ -693,6 +694,7 @@
                 mOverflowBubbles.remove(b);
                 mStateChange.bubbleRemoved(b, reason);
                 mStateChange.removedOverflowBubble = b;
+                mStateChange.showOverflowChanged = mOverflowBubbles.isEmpty();
             }
             if (hasSuppressedBubbleWithKey(key) && shouldRemoveHiddenBubble) {
                 Bubble b = getSuppressedBubbleWithKey(key);
@@ -792,6 +794,9 @@
         }
         ProtoLog.d(WM_SHELL_BUBBLES, "overflowBubble=%s", bubble.getKey());
         mLogger.logOverflowAdd(bubble, reason);
+        if (mOverflowBubbles.isEmpty()) {
+            mStateChange.showOverflowChanged = true;
+        }
         mOverflowBubbles.remove(bubble);
         mOverflowBubbles.add(0, bubble);
         mStateChange.addedOverflowBubble = bubble;
@@ -1231,9 +1236,7 @@
     public void dump(PrintWriter pw) {
         pw.println("BubbleData state:");
         pw.print("  selected: ");
-        pw.println(mSelectedBubble != null
-                ? mSelectedBubble.getKey()
-                : "null");
+        pw.println(getSelectedBubbleKey());
         pw.print("  expanded: ");
         pw.println(mExpanded);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
index 633b01b..18e04d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
@@ -44,6 +44,7 @@
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ContrastColorUtil;
+import com.android.wm.shell.Flags;
 import com.android.wm.shell.R;
 
 import java.util.ArrayList;
@@ -195,7 +196,9 @@
     }
 
     void updateEmptyStateVisibility() {
-        mEmptyState.setVisibility(mOverflowBubbles.isEmpty() ? View.VISIBLE : View.GONE);
+        boolean showEmptyState = mOverflowBubbles.isEmpty()
+                && !Flags.enableOptionalBubbleOverflow();
+        mEmptyState.setVisibility(showEmptyState ? View.VISIBLE : View.GONE);
         mRecyclerView.setVisibility(mOverflowBubbles.isEmpty() ? View.GONE : View.VISIBLE);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index c4bbe32..a35a004 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -324,6 +324,11 @@
         return 0;
     }
 
+    /** Returns whether the IME is visible. */
+    public boolean getIsImeVisible() {
+        return mImeVisible;
+    }
+
     /** Sets whether the IME is visible. **/
     public void setImeVisible(boolean visible, int height) {
         mImeVisible = visible;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 3c788b1..9fabd42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -80,6 +80,7 @@
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.wm.shell.Flags;
 import com.android.wm.shell.R;
 import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener;
@@ -536,7 +537,8 @@
     private OnClickListener mBubbleClickListener = new OnClickListener() {
         @Override
         public void onClick(View view) {
-            mIsDraggingStack = false; // If the touch ended in a click, we're no longer dragging.
+            // If the touch ended in a click, we're no longer dragging.
+            onDraggingEnded();
 
             // Bubble clicks either trigger expansion/collapse or a bubble switch, both of which we
             // shouldn't interrupt. These are quick transitions, so it's not worth trying to adjust
@@ -719,8 +721,7 @@
                 mDismissView.hide();
             }
 
-            mIsDraggingStack = false;
-            mMagnetizedObject = null;
+            onDraggingEnded();
 
             // Hide the stack after a delay, if needed.
             updateTemporarilyInvisibleAnimation(false /* hideImmediately */);
@@ -863,6 +864,7 @@
         }
     };
 
+    private boolean mShowingOverflow;
     private BubbleOverflow mBubbleOverflow;
     private StackEducationView mStackEduView;
     private StackEducationView.Manager mStackEducationViewManager;
@@ -992,18 +994,12 @@
 
         mBubbleOverflow = mBubbleData.getOverflow();
 
-        resetOverflowView();
-        mBubbleContainer.addView(mBubbleOverflow.getIconView(),
-                mBubbleContainer.getChildCount() /* index */,
-                new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
-                        mPositioner.getBubbleSize()));
-        updateOverflow();
-        mBubbleOverflow.getIconView().setOnClickListener((View v) -> {
-            mBubbleData.setShowingOverflow(true);
-            mBubbleData.setSelectedBubble(mBubbleOverflow);
-            mBubbleData.setExpanded(true);
-        });
-
+        if (Flags.enableOptionalBubbleOverflow()) {
+            showOverflow(mBubbleData.hasOverflowBubbles());
+        } else {
+            mShowingOverflow = true; // if the flags not on this is always true
+            setUpOverflow();
+        }
         mScrim = new View(getContext());
         mScrim.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
         mScrim.setBackgroundDrawable(new ColorDrawable(
@@ -1096,6 +1092,7 @@
             } else {
                 maybeShowStackEdu();
             }
+            onDraggingEnded();
         });
 
         animate()
@@ -1153,6 +1150,14 @@
     }
 
     /**
+     * Reset state related to dragging.
+     */
+    private void onDraggingEnded() {
+        mIsDraggingStack = false;
+        mMagnetizedObject = null;
+    }
+
+    /**
      * Sets whether or not the stack should become temporarily invisible by moving off the side of
      * the screen.
      *
@@ -1211,6 +1216,19 @@
         }
     };
 
+    private void setUpOverflow() {
+        resetOverflowView();
+        mBubbleContainer.addView(mBubbleOverflow.getIconView(),
+                mBubbleContainer.getChildCount() /* index */,
+                new FrameLayout.LayoutParams(mBubbleSize, mBubbleSize));
+        updateOverflow();
+        mBubbleOverflow.getIconView().setOnClickListener((View v) -> {
+            mBubbleData.setShowingOverflow(true);
+            mBubbleData.setSelectedBubble(mBubbleOverflow);
+            mBubbleData.setExpanded(true);
+        });
+    }
+
     private void setUpDismissView() {
         if (mDismissView != null) {
             removeView(mDismissView);
@@ -1449,24 +1467,56 @@
                 b.getExpandedView().updateFontSize();
             }
         }
-        if (mBubbleOverflow != null && mBubbleOverflow.getExpandedView() != null) {
+        if (mShowingOverflow && mBubbleOverflow != null
+                && mBubbleOverflow.getExpandedView() != null) {
             mBubbleOverflow.getExpandedView().updateFontSize();
         }
     }
 
     void updateLocale() {
-        if (mBubbleOverflow != null && mBubbleOverflow.getExpandedView() != null) {
+        if (mShowingOverflow && mBubbleOverflow != null
+                && mBubbleOverflow.getExpandedView() != null) {
             mBubbleOverflow.getExpandedView().updateLocale();
         }
     }
 
     private void updateOverflow() {
         mBubbleOverflow.update();
-        mBubbleContainer.reorderView(mBubbleOverflow.getIconView(),
-                mBubbleContainer.getChildCount() - 1 /* index */);
+        if (mShowingOverflow) {
+            mBubbleContainer.reorderView(mBubbleOverflow.getIconView(),
+                    mBubbleContainer.getChildCount() - 1 /* index */);
+        }
         updateOverflowVisibility();
     }
 
+    private void updateOverflowVisibility() {
+        mBubbleOverflow.setVisible(mShowingOverflow
+                && (mIsExpanded || mBubbleData.isShowingOverflow())
+                ? VISIBLE
+                : GONE);
+    }
+
+    private void updateOverflowDotVisibility(boolean expanding) {
+        if (mShowingOverflow && mBubbleOverflow.showDot()) {
+            mBubbleOverflow.getIconView().animateDotScale(expanding ? 1 : 0f, () -> {
+                mBubbleOverflow.setVisible(expanding ? VISIBLE : GONE);
+            });
+        }
+    }
+
+    /**  Sets whether the overflow should be visible or not. */
+    public void showOverflow(boolean showOverflow) {
+        if (!Flags.enableOptionalBubbleOverflow()) return;
+        if (mShowingOverflow != showOverflow) {
+            mShowingOverflow = showOverflow;
+            if (showOverflow) {
+                setUpOverflow();
+            } else if (mBubbleOverflow != null) {
+                resetOverflowView();
+            }
+        }
+    }
+
     /**
      * Handle theme changes.
      */
@@ -1526,7 +1576,10 @@
                 b.getExpandedView().updateDimensions();
             }
         }
-        mBubbleOverflow.getIconView().setLayoutParams(new LayoutParams(mBubbleSize, mBubbleSize));
+        if (mShowingOverflow) {
+            mBubbleOverflow.getIconView().setLayoutParams(
+                    new LayoutParams(mBubbleSize, mBubbleSize));
+        }
         mExpandedAnimationController.updateResources();
         mStackAnimationController.updateResources();
         mDismissView.updateResources();
@@ -1690,7 +1743,7 @@
                     bubble.getIconView().setContentDescription(getResources().getString(
                             R.string.bubble_content_description_single, titleStr, appName));
                 } else {
-                    final int moreCount = mBubbleContainer.getChildCount() - 1;
+                    final int moreCount = getBubbleCount();
                     bubble.getIconView().setContentDescription(getResources().getString(
                             R.string.bubble_content_description_stack,
                             titleStr, appName, moreCount));
@@ -1743,7 +1796,8 @@
 
             View bubbleOverflowIconView =
                     mBubbleOverflow != null ? mBubbleOverflow.getIconView() : null;
-            if (bubbleOverflowIconView != null && !mBubbleData.getBubbles().isEmpty()) {
+            if (mShowingOverflow && bubbleOverflowIconView != null
+                    && !mBubbleData.getBubbles().isEmpty()) {
                 Bubble lastBubble =
                         mBubbleData.getBubbles().get(mBubbleData.getBubbles().size() - 1);
                 View lastBubbleIconView = lastBubble.getIconView();
@@ -1919,20 +1973,6 @@
         }
     }
 
-    private void updateOverflowVisibility() {
-        mBubbleOverflow.setVisible((mIsExpanded || mBubbleData.isShowingOverflow())
-                ? VISIBLE
-                : GONE);
-    }
-
-    private void updateOverflowDotVisibility(boolean expanding) {
-        if (mBubbleOverflow.showDot()) {
-            mBubbleOverflow.getIconView().animateDotScale(expanding ? 1 : 0f, () -> {
-                mBubbleOverflow.setVisible(expanding ? VISIBLE : GONE);
-            });
-        }
-    }
-
     // via BubbleData.Listener
     void updateBubble(Bubble bubble) {
         animateInFlyoutForBubble(bubble);
@@ -2341,8 +2381,8 @@
 
         showScrim(true, null /* runnable */);
         updateBubbleShadows(mIsExpanded);
-        updateBadges(false /* setBadgeForCollapsedStack */);
         mBubbleContainer.setActiveController(mExpandedAnimationController);
+        updateBadges(false /* setBadgeForCollapsedStack */);
         updateOverflowVisibility();
         updatePointerPosition(false /* forIme */);
         mExpandedAnimationController.expandFromStack(() -> {
@@ -3419,8 +3459,9 @@
      * @return the number of bubbles in the stack view.
      */
     public int getBubbleCount() {
-        // Subtract 1 for the overflow button that is always in the bubble container.
-        return mBubbleContainer.getChildCount() - 1;
+        final int childCount = mBubbleContainer.getChildCount();
+        // Subtract 1 for the overflow button if it's showing.
+        return mShowingOverflow ? childCount - 1 : childCount;
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 322088b..1d053f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -297,6 +297,15 @@
             boolean sensitiveNotificationProtectionActive);
 
     /**
+     * Determines whether Bubbles can show notifications.
+     *
+     * <p>Normally bubble notifications are shown by Bubbles, but in some cases the bubble
+     * notification is suppressed and should be shown by the Notifications pipeline as regular
+     * notifications.
+     */
+    boolean canShowBubbleNotification();
+
+    /**
      * A listener to be notified of bubble state changes, used by launcher to render bubbles in
      * its process.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 66f77fa..1eff149 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -33,7 +33,7 @@
 
     oneway void showBubble(in String key, in Rect bubbleBarBounds) = 3;
 
-    oneway void removeBubble(in String key) = 4;
+    oneway void dragBubbleToDismiss(in String key) = 4;
 
     oneway void removeAllBubbles() = 5;
 
@@ -47,5 +47,5 @@
 
     oneway void setBubbleBarBounds(in Rect bubbleBarBounds) = 10;
 
-    oneway void stopBubbleDrag(in String key, in BubbleBarLocation location) = 11;
+    oneway void stopBubbleDrag(in BubbleBarLocation location) = 11;
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
index 2b7a070..d54a6b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
@@ -37,15 +37,11 @@
  */
 public class BubbleBarHandleView extends View {
     private static final long COLOR_CHANGE_DURATION = 120;
-
-    // The handle view is currently rendered as 3 evenly spaced dots.
-    private int mDotSize;
-    private int mDotSpacing;
     // Path used to draw the dots
     private final Path mPath = new Path();
 
-    private @ColorInt int mHandleLightColor;
-    private @ColorInt int mHandleDarkColor;
+    private final @ColorInt int mHandleLightColor;
+    private final @ColorInt int mHandleDarkColor;
     private @Nullable ObjectAnimator mColorChangeAnim;
 
     public BubbleBarHandleView(Context context) {
@@ -63,10 +59,8 @@
     public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mDotSize = getResources().getDimensionPixelSize(
-                R.dimen.bubble_bar_expanded_view_caption_dot_size);
-        mDotSpacing = getResources().getDimensionPixelSize(
-                R.dimen.bubble_bar_expanded_view_caption_dot_spacing);
+        final int handleHeight = getResources().getDimensionPixelSize(
+                R.dimen.bubble_bar_expanded_view_handle_height);
         mHandleLightColor = ContextCompat.getColor(getContext(),
                 R.color.bubble_bar_expanded_view_handle_light);
         mHandleDarkColor = ContextCompat.getColor(getContext(),
@@ -76,27 +70,13 @@
         setOutlineProvider(new ViewOutlineProvider() {
             @Override
             public void getOutline(View view, Outline outline) {
-                final int handleCenterX = view.getWidth() / 2;
                 final int handleCenterY = view.getHeight() / 2;
-                final int handleTotalWidth = mDotSize * 3 + mDotSpacing * 2;
-                final int handleLeft = handleCenterX - handleTotalWidth / 2;
-                final int handleTop = handleCenterY - mDotSize / 2;
-                final int handleBottom = handleTop + mDotSize;
-                RectF dot1 = new RectF(
-                        handleLeft, handleTop,
-                        handleLeft + mDotSize, handleBottom);
-                RectF dot2 = new RectF(
-                        dot1.right + mDotSpacing, handleTop,
-                        dot1.right + mDotSpacing + mDotSize, handleBottom
-                );
-                RectF dot3 = new RectF(
-                        dot2.right + mDotSpacing, handleTop,
-                        dot2.right + mDotSpacing + mDotSize, handleBottom
-                );
+                final int handleTop = handleCenterY - handleHeight / 2;
+                final int handleBottom = handleTop + handleHeight;
+                final int radius = handleHeight / 2;
+                RectF handle = new RectF(/* left = */ 0, handleTop, view.getWidth(), handleBottom);
                 mPath.reset();
-                mPath.addOval(dot1, Path.Direction.CW);
-                mPath.addOval(dot2, Path.Direction.CW);
-                mPath.addOval(dot3, Path.Direction.CW);
+                mPath.addRoundRect(handle, radius, radius, Path.Direction.CW);
                 outline.setPath(mPath);
             }
         });
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index a351cef..123cc7e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -356,7 +356,7 @@
     }
 
     /** Updates the expanded view size and position. */
-    private void updateExpandedView() {
+    public void updateExpandedView() {
         if (mExpandedView == null || mExpandedBubble == null) return;
         boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
         mPositioner.getBubbleBarExpandedViewBounds(mPositioner.isBubbleBarOnLeft(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt
index 3b3974d..651bf02 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt
@@ -22,7 +22,6 @@
 import android.view.LayoutInflater
 import android.view.View
 import android.widget.FrameLayout
-import androidx.annotation.VisibleForTesting
 import androidx.core.view.updateLayoutParams
 import com.android.wm.shell.R
 import com.android.wm.shell.bubbles.BubblePositioner
@@ -79,7 +78,11 @@
 
     override fun updateLocation(location: BubbleBarLocation) {
         val view = dropTargetView ?: return
-        getBounds(location.isOnLeft(view.isLayoutRtl), tempRect)
+        positioner.getBubbleBarExpandedViewBounds(
+            location.isOnLeft(view.isLayoutRtl),
+            false /* isOverflowExpanded */,
+            tempRect
+        )
         view.updateLayoutParams<FrameLayout.LayoutParams> {
             width = tempRect.width()
             height = tempRect.height()
@@ -87,17 +90,4 @@
         view.x = tempRect.left.toFloat()
         view.y = tempRect.top.toFloat()
     }
-
-    private fun getBounds(onLeft: Boolean, out: Rect) {
-        positioner.getBubbleBarExpandedViewBounds(onLeft, false /* isOverflowExpanded */, out)
-        val centerX = out.centerX()
-        val centerY = out.centerY()
-        out.scale(DROP_TARGET_SCALE)
-        // Move rect center back to the same position as before scale
-        out.offset(centerX - out.centerX(), centerY - out.centerY())
-    }
-
-    companion object {
-        @VisibleForTesting const val DROP_TARGET_SCALE = 0.9f
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index 98dccbb..da414cc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -389,9 +389,6 @@
         public void dispatchDragEvent(DragEvent event) {}
 
         @Override
-        public void updatePointerIcon(float x, float y) {}
-
-        @Override
         public void dispatchWindowShown() {}
 
         @Override
@@ -409,5 +406,10 @@
                 // ignore
             }
         }
+
+        @Override
+        public void dumpWindow(ParcelFileDescriptor pfd) {
+
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
index e5f6c37..ec3c601 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.Point;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -49,6 +50,10 @@
     public String unsupressedBubbleKey;
     @Nullable
     public BubbleBarLocation bubbleBarLocation;
+    @Nullable
+    public Point expandedViewDropTargetSize;
+    public boolean showOverflowChanged;
+    public boolean showOverflow;
 
     // This is only populated if bubbles have been removed.
     public List<RemovedBubble> removedBubbles = new ArrayList<>();
@@ -81,12 +86,16 @@
         suppressedBubbleKey = parcel.readString();
         unsupressedBubbleKey = parcel.readString();
         removedBubbles = parcel.readParcelableList(new ArrayList<>(),
-                RemovedBubble.class.getClassLoader());
+                RemovedBubble.class.getClassLoader(), RemovedBubble.class);
         parcel.readStringList(bubbleKeysInOrder);
         currentBubbleList = parcel.readParcelableList(new ArrayList<>(),
-                BubbleInfo.class.getClassLoader());
+                BubbleInfo.class.getClassLoader(), BubbleInfo.class);
         bubbleBarLocation = parcel.readParcelable(BubbleBarLocation.class.getClassLoader(),
                 BubbleBarLocation.class);
+        expandedViewDropTargetSize = parcel.readParcelable(Point.class.getClassLoader(),
+                Point.class);
+        showOverflowChanged = parcel.readBoolean();
+        showOverflow = parcel.readBoolean();
     }
 
     /**
@@ -102,9 +111,11 @@
                 || suppressedBubbleKey != null
                 || unsupressedBubbleKey != null
                 || !currentBubbleList.isEmpty()
-                || bubbleBarLocation != null;
+                || bubbleBarLocation != null
+                || showOverflowChanged;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "BubbleBarUpdate{"
@@ -121,6 +132,9 @@
                 + " bubbles=" + bubbleKeysInOrder
                 + " currentBubbleList=" + currentBubbleList
                 + " bubbleBarLocation=" + bubbleBarLocation
+                + " expandedViewDropTargetSize=" + expandedViewDropTargetSize
+                + " showOverflowChanged=" + showOverflowChanged
+                + " showOverflow=" + showOverflow
                 + " }";
     }
 
@@ -144,6 +158,9 @@
         parcel.writeStringList(bubbleKeysInOrder);
         parcel.writeParcelableList(currentBubbleList, flags);
         parcel.writeParcelable(bubbleBarLocation, flags);
+        parcel.writeParcelable(expandedViewDropTargetSize, flags);
+        parcel.writeBoolean(showOverflowChanged);
+        parcel.writeBoolean(showOverflow);
     }
 
     /**
@@ -157,10 +174,11 @@
 
     @NonNull
     public static final Creator<BubbleBarUpdate> CREATOR =
-            new Creator<BubbleBarUpdate>() {
+            new Creator<>() {
                 public BubbleBarUpdate createFromParcel(Parcel source) {
                     return new BubbleBarUpdate(source);
                 }
+
                 public BubbleBarUpdate[] newArray(int size) {
                     return new BubbleBarUpdate[size];
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/OWNERS
new file mode 100644
index 0000000..08c7031
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/OWNERS
@@ -0,0 +1,6 @@
+# WM shell sub-module bubble owner
+madym@google.com
+atsjenk@google.com
+liranb@google.com
+sukeshram@google.com
+mpodolian@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
index 1e30d8f..579a794 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
@@ -16,11 +16,14 @@
 package com.android.wm.shell.common.pip
 
 import android.app.ActivityTaskManager
+import android.app.AppGlobals
 import android.app.RemoteAction
 import android.app.WindowConfiguration
 import android.content.ComponentName
 import android.content.Context
+import android.content.pm.PackageManager
 import android.os.RemoteException
+import android.os.SystemProperties
 import android.util.DisplayMetrics
 import android.util.Log
 import android.util.Pair
@@ -135,7 +138,24 @@
         }
     }
 
+    private var isPip2ExperimentEnabled: Boolean? = null
+
+    /**
+     * Returns true if PiP2 implementation should be used. Besides the trunk stable flag,
+     * system property can be used to override this read only flag during development.
+     * It's currently limited to phone form factor, i.e., not enabled on ARC / TV.
+     */
     @JvmStatic
-    val isPip2ExperimentEnabled: Boolean
-        get() = Flags.enablePip2Implementation()
+    fun isPip2ExperimentEnabled(): Boolean {
+        if (isPip2ExperimentEnabled == null) {
+            val isArc = AppGlobals.getPackageManager().hasSystemFeature(
+                "org.chromium.arc", 0)
+            val isTv = AppGlobals.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_LEANBACK, 0)
+            isPip2ExperimentEnabled = SystemProperties.getBoolean(
+                    "persist.wm_shell.pip2", false) ||
+                    (Flags.enablePip2Implementation() && !isArc && !isTv)
+        }
+        return isPip2ExperimentEnabled as Boolean
+    }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index a87116e..2234041 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -185,7 +185,7 @@
                 nextTarget = snapAlgorithm.getDismissStartTarget();
             }
             if (nextTarget != null) {
-                mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), nextTarget);
+                mSplitLayout.snapToTarget(mSplitLayout.getDividerPosition(), nextTarget);
                 return true;
             }
             return super.performAccessibilityAction(host, action, args);
@@ -345,9 +345,9 @@
                     mMoving = true;
                 }
                 if (mMoving) {
-                    final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
+                    final int position = mSplitLayout.getDividerPosition() + touchPos - mStartPos;
                     mLastDraggingPosition = position;
-                    mSplitLayout.updateDivideBounds(position);
+                    mSplitLayout.updateDividerBounds(position, true /* shouldUseParallaxEffect */);
                 }
                 break;
             case MotionEvent.ACTION_UP:
@@ -363,7 +363,7 @@
                 final float velocity = isLeftRightSplit
                         ? mVelocityTracker.getXVelocity()
                         : mVelocityTracker.getYVelocity();
-                final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
+                final int position = mSplitLayout.getDividerPosition() + touchPos - mStartPos;
                 final DividerSnapAlgorithm.SnapTarget snapTarget =
                         mSplitLayout.findSnapTarget(position, velocity, false /* hardDismiss */);
                 mSplitLayout.snapToTarget(position, snapTarget);
@@ -472,12 +472,12 @@
         mInteractive = interactive;
         mHideHandle = hideHandle;
         if (!mInteractive && mHideHandle && mMoving) {
-            final int position = mSplitLayout.getDividePosition();
-            mSplitLayout.flingDividePosition(
+            final int position = mSplitLayout.getDividerPosition();
+            mSplitLayout.flingDividerPosition(
                     mLastDraggingPosition,
                     position,
                     mSplitLayout.FLING_RESIZE_DURATION,
-                    () -> mSplitLayout.setDividePosition(position, true /* applyLayoutChange */));
+                    () -> mSplitLayout.setDividerPosition(position, true /* applyLayoutChange */));
             mMoving = false;
         }
         releaseTouching();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 30eb8b5d..de016d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -31,7 +31,6 @@
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.graphics.Color;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -57,7 +56,13 @@
 import java.util.function.Consumer;
 
 /**
- * Handles split decor like showing resizing hint for a specific split.
+ * Handles additional layers over a running task in a split pair, for example showing a veil with an
+ * app icon when the task is being resized (usually to hide weird layouts while the app is being
+ * stretched). One SplitDecorManager is initialized on each window.
+ * <br>
+ * Currently, we show a veil when:
+ *  a) Task is resizing down from a fullscreen window.
+ *  b) Task is being stretched past its original bounds.
  */
 public class SplitDecorManager extends WindowlessWindowManager {
     private static final String TAG = SplitDecorManager.class.getSimpleName();
@@ -78,7 +83,11 @@
 
     private boolean mShown;
     private boolean mIsResizing;
-    private final Rect mOldBounds = new Rect();
+    /** The original bounds of the main task, captured at the beginning of a resize transition. */
+    private final Rect mOldMainBounds = new Rect();
+    /** The original bounds of the side task, captured at the beginning of a resize transition. */
+    private final Rect mOldSideBounds = new Rect();
+    /** The current bounds of the main task, mid-resize. */
     private final Rect mResizingBounds = new Rect();
     private final Rect mTempRect = new Rect();
     private ValueAnimator mFadeAnimator;
@@ -184,29 +193,38 @@
         mResizingIconView = null;
         mIsResizing = false;
         mShown = false;
-        mOldBounds.setEmpty();
+        mOldMainBounds.setEmpty();
+        mOldSideBounds.setEmpty();
         mResizingBounds.setEmpty();
     }
 
     /** Showing resizing hint. */
     public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
             Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY,
-            boolean immediately) {
+            boolean immediately, float[] veilColor) {
         if (mResizingIconView == null) {
             return;
         }
 
         if (!mIsResizing) {
             mIsResizing = true;
-            mOldBounds.set(newBounds);
+            mOldMainBounds.set(newBounds);
+            mOldSideBounds.set(sideBounds);
         }
         mResizingBounds.set(newBounds);
         mOffsetX = offsetX;
         mOffsetY = offsetY;
 
-        final boolean show =
-                newBounds.width() > mOldBounds.width() || newBounds.height() > mOldBounds.height();
-        final boolean update = show != mShown;
+        // Show a veil when:
+        //  a) Task is resizing down from a fullscreen window.
+        //  b) Task is being stretched past its original bounds.
+        final boolean isResizingDownFromFullscreen =
+                mOldSideBounds.width() <= 1 || mOldSideBounds.height() <= 1;
+        final boolean isStretchingPastOriginalBounds =
+                newBounds.width() > mOldMainBounds.width()
+                        || newBounds.height() > mOldMainBounds.height();
+        final boolean showVeil = isResizingDownFromFullscreen || isStretchingPastOriginalBounds;
+        final boolean update = showVeil != mShown;
         if (update && mFadeAnimator != null && mFadeAnimator.isRunning()) {
             // If we need to animate and animator still running, cancel it before we ensure both
             // background and icon surfaces are non null for next animation.
@@ -216,18 +234,18 @@
         if (mBackgroundLeash == null) {
             mBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
                     RESIZING_BACKGROUND_SURFACE_NAME, mSurfaceSession);
-            t.setColor(mBackgroundLeash, getResizingBackgroundColor(resizingTask))
+            t.setColor(mBackgroundLeash, veilColor)
                     .setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1);
         }
 
         if (mGapBackgroundLeash == null && !immediately) {
             final boolean isLandscape = newBounds.height() == sideBounds.height();
-            final int left = isLandscape ? mOldBounds.width() : 0;
-            final int top = isLandscape ? 0 : mOldBounds.height();
+            final int left = isLandscape ? mOldMainBounds.width() : 0;
+            final int top = isLandscape ? 0 : mOldMainBounds.height();
             mGapBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
                     GAP_BACKGROUND_SURFACE_NAME, mSurfaceSession);
             // Fill up another side bounds area.
-            t.setColor(mGapBackgroundLeash, getResizingBackgroundColor(resizingTask))
+            t.setColor(mGapBackgroundLeash, veilColor)
                     .setLayer(mGapBackgroundLeash, Integer.MAX_VALUE - 2)
                     .setPosition(mGapBackgroundLeash, left, top)
                     .setWindowCrop(mGapBackgroundLeash, sideBounds.width(), sideBounds.height());
@@ -251,12 +269,12 @@
 
         if (update) {
             if (immediately) {
-                t.setVisibility(mBackgroundLeash, show);
-                t.setVisibility(mIconLeash, show);
+                t.setVisibility(mBackgroundLeash, showVeil);
+                t.setVisibility(mIconLeash, showVeil);
             } else {
-                startFadeAnimation(show, false, null);
+                startFadeAnimation(showVeil, false, null);
             }
-            mShown = show;
+            mShown = showVeil;
         }
     }
 
@@ -309,7 +327,8 @@
         mIsResizing = false;
         mOffsetX = 0;
         mOffsetY = 0;
-        mOldBounds.setEmpty();
+        mOldMainBounds.setEmpty();
+        mOldSideBounds.setEmpty();
         mResizingBounds.setEmpty();
         if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
             if (!mShown) {
@@ -346,14 +365,14 @@
 
     /** Screenshot host leash and attach on it if meet some conditions */
     public void screenshotIfNeeded(SurfaceControl.Transaction t) {
-        if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) {
+        if (!mShown && mIsResizing && !mOldMainBounds.equals(mResizingBounds)) {
             if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
                 mScreenshotAnimator.cancel();
             } else if (mScreenshot != null) {
                 t.remove(mScreenshot);
             }
 
-            mTempRect.set(mOldBounds);
+            mTempRect.set(mOldMainBounds);
             mTempRect.offsetTo(0, 0);
             mScreenshot = ScreenshotUtils.takeScreenshot(t, mHostLeash, mTempRect,
                     Integer.MAX_VALUE - 1);
@@ -364,7 +383,7 @@
     public void setScreenshotIfNeeded(SurfaceControl screenshot, SurfaceControl.Transaction t) {
         if (screenshot == null || !screenshot.isValid()) return;
 
-        if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) {
+        if (!mShown && mIsResizing && !mOldMainBounds.equals(mResizingBounds)) {
             if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
                 mScreenshotAnimator.cancel();
             } else if (mScreenshot != null) {
@@ -465,9 +484,4 @@
             mIcon = null;
         }
     }
-
-    private static float[] getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) {
-        final int taskBgColor = taskInfo.taskDescription.getBackgroundColor();
-        return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).getComponents();
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 6b2d544..8331654 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -78,7 +78,7 @@
 
 /**
  * Records and handles layout of splits. Helps to calculate proper bounds when configuration or
- * divide position changes.
+ * divider position changes.
  */
 public final class SplitLayout implements DisplayInsetsController.OnInsetsChangedListener {
     private static final String TAG = "SplitLayout";
@@ -278,7 +278,7 @@
         return mSplitWindowManager == null ? null : mSplitWindowManager.getSurfaceControl();
     }
 
-    int getDividePosition() {
+    int getDividerPosition() {
         return mDividerPosition;
     }
 
@@ -489,20 +489,20 @@
     public void setDividerAtBorder(boolean start) {
         final int pos = start ? mDividerSnapAlgorithm.getDismissStartTarget().position
                 : mDividerSnapAlgorithm.getDismissEndTarget().position;
-        setDividePosition(pos, false /* applyLayoutChange */);
+        setDividerPosition(pos, false /* applyLayoutChange */);
     }
 
     /**
      * Updates bounds with the passing position. Usually used to update recording bounds while
      * performing animation or dragging divider bar to resize the splits.
      */
-    void updateDivideBounds(int position) {
+    void updateDividerBounds(int position, boolean shouldUseParallaxEffect) {
         updateBounds(position);
         mSplitLayoutHandler.onLayoutSizeChanging(this, mSurfaceEffectPolicy.mParallaxOffset.x,
-                mSurfaceEffectPolicy.mParallaxOffset.y);
+                mSurfaceEffectPolicy.mParallaxOffset.y, shouldUseParallaxEffect);
     }
 
-    void setDividePosition(int position, boolean applyLayoutChange) {
+    void setDividerPosition(int position, boolean applyLayoutChange) {
         mDividerPosition = position;
         updateBounds(mDividerPosition);
         if (applyLayoutChange) {
@@ -511,14 +511,14 @@
     }
 
     /**
-     * Updates divide position and split bounds base on the ratio within root bounds. Falls back
+     * Updates divider position and split bounds base on the ratio within root bounds. Falls back
      * to middle position if the provided SnapTarget is not supported.
      */
     public void setDivideRatio(@PersistentSnapPosition int snapPosition) {
         final DividerSnapAlgorithm.SnapTarget snapTarget = mDividerSnapAlgorithm.findSnapTarget(
                 snapPosition);
 
-        setDividePosition(snapTarget != null
+        setDividerPosition(snapTarget != null
                 ? snapTarget.position
                 : mDividerSnapAlgorithm.getMiddleTarget().position,
                 false /* applyLayoutChange */);
@@ -546,24 +546,24 @@
     }
 
     /**
-     * Sets new divide position and updates bounds correspondingly. Notifies listener if the new
+     * Sets new divider position and updates bounds correspondingly. Notifies listener if the new
      * target indicates dismissing split.
      */
     public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) {
         switch (snapTarget.snapPosition) {
             case SNAP_TO_START_AND_DISMISS:
-                flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
+                flingDividerPosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
                         () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */,
                                 EXIT_REASON_DRAG_DIVIDER));
                 break;
             case SNAP_TO_END_AND_DISMISS:
-                flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
+                flingDividerPosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
                         () -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */,
                                 EXIT_REASON_DRAG_DIVIDER));
                 break;
             default:
-                flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
-                        () -> setDividePosition(snapTarget.position, true /* applyLayoutChange */));
+                flingDividerPosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
+                        () -> setDividerPosition(snapTarget.position, true /* applyLayoutChange */));
                 break;
         }
     }
@@ -615,19 +615,19 @@
     public void flingDividerToDismiss(boolean toEnd, int reason) {
         final int target = toEnd ? mDividerSnapAlgorithm.getDismissEndTarget().position
                 : mDividerSnapAlgorithm.getDismissStartTarget().position;
-        flingDividePosition(getDividePosition(), target, FLING_EXIT_DURATION,
+        flingDividerPosition(getDividerPosition(), target, FLING_EXIT_DURATION,
                 () -> mSplitLayoutHandler.onSnappedToDismiss(toEnd, reason));
     }
 
     /** Fling divider from current position to center position. */
     public void flingDividerToCenter() {
         final int pos = mDividerSnapAlgorithm.getMiddleTarget().position;
-        flingDividePosition(getDividePosition(), pos, FLING_ENTER_DURATION,
-                () -> setDividePosition(pos, true /* applyLayoutChange */));
+        flingDividerPosition(getDividerPosition(), pos, FLING_ENTER_DURATION,
+                () -> setDividerPosition(pos, true /* applyLayoutChange */));
     }
 
     @VisibleForTesting
-    void flingDividePosition(int from, int to, int duration,
+    void flingDividerPosition(int from, int to, int duration,
             @Nullable Runnable flingFinishedCallback) {
         if (from == to) {
             if (flingFinishedCallback != null) {
@@ -647,7 +647,9 @@
                 .setDuration(duration);
         mDividerFlingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
         mDividerFlingAnimator.addUpdateListener(
-                animation -> updateDivideBounds((int) animation.getAnimatedValue()));
+                animation -> updateDividerBounds(
+                        (int) animation.getAnimatedValue(), false /* shouldUseParallaxEffect */)
+        );
         mDividerFlingAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
@@ -897,7 +899,8 @@
          * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl,
          * SurfaceControl, SurfaceControl, boolean)
          */
-        void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY);
+        void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY,
+                boolean shouldUseParallaxEffect);
 
         /**
          * Calls when finish resizing the split bounds.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index f9259e7..e8226051 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -16,8 +16,6 @@
 
 package com.android.wm.shell.common.split;
 
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED;
-
 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -26,25 +24,18 @@
 
 import android.app.ActivityManager;
 import android.app.PendingIntent;
-import android.content.ComponentName;
 import android.content.Intent;
-import android.content.pm.LauncherApps;
-import android.content.pm.ShortcutInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.Color;
 import android.graphics.Rect;
-import android.os.UserHandle;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.wm.shell.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
 
-import java.util.Arrays;
-import java.util.List;
-
 /** Helper utility class for split screen components to use. */
 public class SplitScreenUtils {
     /** Reverse the split position. */
@@ -137,4 +128,10 @@
             return isLandscape;
         }
     }
+
+    /** Returns the specified background color that matches a RunningTaskInfo. */
+    public static Color getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) {
+        final int taskBgColor = taskInfo.taskDescription.getBackgroundColor();
+        return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
index cf3ad42..713d04bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
@@ -194,6 +194,10 @@
         return mHideSizeCompatRestartButtonTolerance;
     }
 
+    int getDefaultHideRestartButtonTolerance() {
+        return MAX_PERCENTAGE_VAL;
+    }
+
     boolean getHasSeenLetterboxEducation(int userId) {
         return mLetterboxEduSharedPreferences
                 .getBoolean(dontShowLetterboxEduKey(userId), /* default= */ false);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 5c292f1..bfac24b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -188,6 +188,11 @@
      */
     private boolean mHasShownUserAspectRatioSettingsButton = false;
 
+    /**
+     * This is true when the rechability education is displayed for the first time.
+     */
+    private boolean mIsFirstReachabilityEducationRunning;
+
     public CompatUIController(@NonNull Context context,
             @NonNull ShellInit shellInit,
             @NonNull ShellController shellController,
@@ -252,9 +257,35 @@
             removeLayouts(taskInfo.taskId);
             return;
         }
-
+        // We're showing the first reachability education so we ignore incoming TaskInfo
+        // until the education flow has completed or we double tap.
+        if (mIsFirstReachabilityEducationRunning) {
+            return;
+        }
+        if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) {
+            if (taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled) {
+                createOrUpdateLetterboxEduLayout(taskInfo, taskListener);
+            } else if (!taskInfo.appCompatTaskInfo.isFromLetterboxDoubleTap) {
+                // In this case the app is letterboxed and the letterbox education
+                // is disabled. In this case we need to understand if it's the first
+                // time we show the reachability education. When this is happening
+                // we need to ignore all the incoming TaskInfo until the education
+                // completes. If we come from a double tap we follow the normal flow.
+                final boolean topActivityPillarboxed =
+                        taskInfo.appCompatTaskInfo.isTopActivityPillarboxed();
+                final boolean isFirstTimeHorizontalReachabilityEdu = topActivityPillarboxed
+                        && !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(taskInfo);
+                final boolean isFirstTimeVerticalReachabilityEdu = !topActivityPillarboxed
+                        && !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(taskInfo);
+                if (isFirstTimeHorizontalReachabilityEdu || isFirstTimeVerticalReachabilityEdu) {
+                    mIsFirstReachabilityEducationRunning = true;
+                    mCompatUIConfiguration.setSeenLetterboxEducation(taskInfo.userId);
+                    createOrUpdateReachabilityEduLayout(taskInfo, taskListener);
+                    return;
+                }
+            }
+        }
         createOrUpdateCompatLayout(taskInfo, taskListener);
-        createOrUpdateLetterboxEduLayout(taskInfo, taskListener);
         createOrUpdateRestartDialogLayout(taskInfo, taskListener);
         if (mCompatUIConfiguration.getHasSeenLetterboxEducation(taskInfo.userId)) {
             createOrUpdateReachabilityEduLayout(taskInfo, taskListener);
@@ -589,6 +620,7 @@
     private void onInitialReachabilityEduDismissed(@NonNull TaskInfo taskInfo,
             @NonNull ShellTaskOrganizer.TaskListener taskListener) {
         // We need to update the UI otherwise it will not be shown until the user relaunches the app
+        mIsFirstReachabilityEducationRunning = false;
         createOrUpdateUserAspectRatioSettingsLayout(taskInfo, taskListener);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 4e5c2fa..3ab1fad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -20,11 +20,11 @@
 import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
 import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
 import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
 import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.AppCompatTaskInfo;
 import android.app.CameraCompatTaskInfo.CameraCompatControlState;
 import android.app.TaskInfo;
 import android.content.Context;
@@ -81,6 +81,10 @@
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
         mCallback = callback;
         mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
+        if (Flags.enableDesktopWindowingMode() && Flags.enableWindowingDynamicInitialBounds()) {
+            // Don't show the SCM button for freeform tasks
+            mHasSizeCompat &= !taskInfo.isFreeform();
+        }
         mCameraCompatControlState =
                 taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState;
         mCompatUIHintsState = compatUIHintsState;
@@ -136,6 +140,10 @@
         final boolean prevHasSizeCompat = mHasSizeCompat;
         final int prevCameraCompatControlState = mCameraCompatControlState;
         mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
+        if (Flags.enableDesktopWindowingMode() && Flags.enableWindowingDynamicInitialBounds()) {
+            // Don't show the SCM button for freeform tasks
+            mHasSizeCompat &= !taskInfo.isFreeform();
+        }
         mCameraCompatControlState =
                 taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState;
 
@@ -219,14 +227,30 @@
 
     @VisibleForTesting
     boolean shouldShowSizeCompatRestartButton(@NonNull TaskInfo taskInfo) {
-        if (!Flags.allowHideScmButton()) {
+        // Always show button if display is phone sized.
+        if (!Flags.allowHideScmButton() || taskInfo.configuration.smallestScreenWidthDp
+                < LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP) {
             return true;
         }
-        final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo;
-        final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
-        final int letterboxArea = computeArea(appCompatTaskInfo.topActivityLetterboxWidth,
-                appCompatTaskInfo.topActivityLetterboxHeight);
-        final int taskArea = computeArea(taskBounds.width(), taskBounds.height());
+
+        final int letterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxWidth;
+        final int letterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxHeight;
+        final Rect stableBounds = getTaskStableBounds();
+        final int appWidth = stableBounds.width();
+        final int appHeight = stableBounds.height();
+        // App is floating, should always show restart button.
+        if (appWidth > letterboxWidth && appHeight > letterboxHeight) {
+            return true;
+        }
+        // If app fills the width of the display, don't show restart button (for landscape apps)
+        // if device has a custom tolerance value.
+        if (mHideScmTolerance != mCompatUIConfiguration.getDefaultHideRestartButtonTolerance()
+                && appWidth == letterboxWidth)  {
+            return false;
+        }
+
+        final int letterboxArea = letterboxWidth * letterboxHeight;
+        final int taskArea = appWidth * appHeight;
         if (letterboxArea == 0 || taskArea == 0) {
             return false;
         }
@@ -234,13 +258,6 @@
         return percentageAreaOfLetterboxInTask < mHideScmTolerance;
     }
 
-    private int computeArea(int width, int height) {
-        if (width == 0 || height == 0) {
-            return 0;
-        }
-        return width * height;
-    }
-
     private void updateVisibilityOfViews() {
         if (mLayout == null) {
             return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 17121c8..991fbaf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -72,7 +72,6 @@
 import com.android.wm.shell.compatui.CompatUIController;
 import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
 import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
@@ -88,6 +87,7 @@
 import com.android.wm.shell.recents.RecentTasks;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.shared.DesktopModeStatus;
 import com.android.wm.shell.shared.ShellTransitions;
 import com.android.wm.shell.shared.annotations.ShellAnimationThread;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
@@ -891,13 +891,13 @@
 
     @WMSingleton
     @Provides
-    static Optional<DesktopTasksController> providesDesktopTasksController(
+    static Optional<DesktopTasksController> providesDesktopTasksController(Context context,
             @DynamicOverride Optional<Lazy<DesktopTasksController>> desktopTasksController) {
         // Use optional-of-lazy for the dependency that this provider relies on.
         // Lazy ensures that this provider will not be the cause the dependency is created
         // when it will not be returned due to the condition below.
         return desktopTasksController.flatMap((lazy)-> {
-            if (DesktopModeStatus.isEnabled()) {
+            if (DesktopModeStatus.canEnterDesktopMode(context)) {
                 return Optional.of(lazy.get());
             }
             return Optional.empty();
@@ -910,13 +910,13 @@
 
     @WMSingleton
     @Provides
-    static Optional<DesktopModeTaskRepository> provideDesktopTaskRepository(
+    static Optional<DesktopModeTaskRepository> provideDesktopTaskRepository(Context context,
             @DynamicOverride Optional<Lazy<DesktopModeTaskRepository>> desktopModeTaskRepository) {
         // Use optional-of-lazy for the dependency that this provider relies on.
         // Lazy ensures that this provider will not be the cause the dependency is created
         // when it will not be returned due to the condition below.
         return desktopModeTaskRepository.flatMap((lazy)-> {
-            if (DesktopModeStatus.isEnabled()) {
+            if (DesktopModeStatus.canEnterDesktopMode(context)) {
                 return Optional.of(lazy.get());
             }
             return Optional.empty();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index b574b81..4e9e8f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -57,7 +57,6 @@
 import com.android.wm.shell.dagger.pip.PipModule;
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
 import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
@@ -77,6 +76,7 @@
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.shared.DesktopModeStatus;
 import com.android.wm.shell.shared.annotations.ShellAnimationThread;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
@@ -100,6 +100,7 @@
 import com.android.wm.shell.unfold.qualifier.UnfoldTransition;
 import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel;
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel;
+import com.android.wm.shell.windowdecor.ResizeHandleSizeRepository;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import dagger.Binds;
@@ -220,8 +221,9 @@
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopTasksController> desktopTasksController,
-            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
-        if (DesktopModeStatus.isEnabled()) {
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+            ResizeHandleSizeRepository resizeHandleSizeRepository) {
+        if (DesktopModeStatus.canEnterDesktopMode(context)) {
             return new DesktopModeWindowDecorViewModel(
                     context,
                     mainExecutor,
@@ -237,7 +239,8 @@
                     syncQueue,
                     transitions,
                     desktopTasksController,
-                    rootTaskDisplayAreaOrganizer);
+                    rootTaskDisplayAreaOrganizer,
+                    resizeHandleSizeRepository);
         }
         return new CaptionWindowDecorViewModel(
                 context,
@@ -247,7 +250,8 @@
                 displayController,
                 rootTaskDisplayAreaOrganizer,
                 syncQueue,
-                transitions);
+                transitions,
+                resizeHandleSizeRepository);
     }
 
     //
@@ -278,8 +282,8 @@
         ShellInit init = FreeformComponents.isFreeformEnabled(context)
                 ? shellInit
                 : null;
-        return new FreeformTaskListener(init, shellTaskOrganizer, desktopModeTaskRepository,
-                windowDecorViewModel);
+        return new FreeformTaskListener(context, init, shellTaskOrganizer,
+                desktopModeTaskRepository, windowDecorViewModel);
     }
 
     @WMSingleton
@@ -529,16 +533,19 @@
                 exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler,
                 dragToDesktopTransitionHandler, desktopModeTaskRepository,
                 desktopModeLoggerTransitionObserver, launchAdjacentController,
-                recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter);
+                recentsTransitionHandler, multiInstanceHelper,
+                mainExecutor, desktopTasksLimiter);
     }
 
     @WMSingleton
     @Provides
     static Optional<DesktopTasksLimiter> provideDesktopTasksLimiter(
+            Context context,
             Transitions transitions,
             @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
             ShellTaskOrganizer shellTaskOrganizer) {
-        if (!DesktopModeStatus.isEnabled() || !Flags.enableDesktopWindowingTaskLimit()) {
+        if (!DesktopModeStatus.canEnterDesktopMode(context)
+                || !Flags.enableDesktopWindowingTaskLimit()) {
             return Optional.empty();
         }
         return Optional.of(
@@ -592,23 +599,26 @@
     @WMSingleton
     @Provides
     static Optional<DesktopTasksTransitionObserver> provideDesktopTasksTransitionObserver(
+            Context context,
             Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             Transitions transitions,
             ShellInit shellInit
     ) {
         return desktopModeTaskRepository.flatMap(repository ->
-                Optional.of(new DesktopTasksTransitionObserver(repository, transitions, shellInit))
+                Optional.of(new DesktopTasksTransitionObserver(
+                        context, repository, transitions, shellInit))
         );
     }
 
     @WMSingleton
     @Provides
     static DesktopModeLoggerTransitionObserver provideDesktopModeLoggerTransitionObserver(
+            Context context,
             ShellInit shellInit,
             Transitions transitions,
             DesktopModeEventLogger desktopModeEventLogger) {
         return new DesktopModeLoggerTransitionObserver(
-                shellInit, transitions, desktopModeEventLogger);
+                context, shellInit, transitions, desktopModeEventLogger);
     }
 
     @WMSingleton
@@ -617,6 +627,12 @@
         return new DesktopModeEventLogger();
     }
 
+    @WMSingleton
+    @Provides
+    static ResizeHandleSizeRepository provideResizeHandleSizeRepository() {
+        return new ResizeHandleSizeRepository();
+    }
+
     //
     // Drag and drop
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java
index 795bc1a..d2895b1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java
@@ -16,9 +16,9 @@
 
 package com.android.wm.shell.dagger.back;
 
-import com.android.wm.shell.back.CrossActivityBackAnimation;
 import com.android.wm.shell.back.CrossTaskBackAnimation;
-import com.android.wm.shell.back.CustomizeActivityAnimation;
+import com.android.wm.shell.back.CustomCrossActivityBackAnimation;
+import com.android.wm.shell.back.DefaultCrossActivityBackAnimation;
 import com.android.wm.shell.back.ShellBackAnimation;
 import com.android.wm.shell.back.ShellBackAnimationRegistry;
 
@@ -47,7 +47,7 @@
     @Binds
     @ShellBackAnimation.CrossActivity
     ShellBackAnimation bindCrossActivityShellBackAnimation(
-            CrossActivityBackAnimation crossActivityBackAnimation);
+            DefaultCrossActivityBackAnimation defaultCrossActivityBackAnimation);
 
     /** Default cross task back animation */
     @Binds
@@ -59,5 +59,5 @@
     @Binds
     @ShellBackAnimation.CustomizeActivity
     ShellBackAnimation provideCustomizeActivityShellBackAnimation(
-            CustomizeActivityAnimation customizeActivityAnimation);
+            CustomCrossActivityBackAnimation customCrossActivityBackAnimation);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 6e61f22..01364d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -132,6 +132,8 @@
             PhonePipMenuController menuPhoneController,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             @NonNull PipBoundsState pipBoundsState,
+            @NonNull PipTransitionState pipTransitionState,
+            @NonNull PipScheduler pipScheduler,
             @NonNull SizeSpecSource sizeSpecSource,
             PipMotionHelper pipMotionHelper,
             FloatingContentCoordinator floatingContentCoordinator,
@@ -139,8 +141,9 @@
             @ShellMainThread ShellExecutor mainExecutor,
             Optional<PipPerfHintController> pipPerfHintControllerOptional) {
         return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
-                pipBoundsState, sizeSpecSource, pipMotionHelper, floatingContentCoordinator,
-                pipUiEventLogger, mainExecutor, pipPerfHintControllerOptional);
+                pipBoundsState, pipTransitionState, pipScheduler, sizeSpecSource, pipMotionHelper,
+                floatingContentCoordinator, pipUiEventLogger, mainExecutor,
+                pipPerfHintControllerOptional);
     }
 
     @WMSingleton
@@ -149,9 +152,13 @@
             PipBoundsState pipBoundsState, PhonePipMenuController menuController,
             PipSnapAlgorithm pipSnapAlgorithm,
             FloatingContentCoordinator floatingContentCoordinator,
-            Optional<PipPerfHintController> pipPerfHintControllerOptional) {
+            PipScheduler pipScheduler,
+            Optional<PipPerfHintController> pipPerfHintControllerOptional,
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipTransitionState pipTransitionState) {
         return new PipMotionHelper(context, pipBoundsState, menuController, pipSnapAlgorithm,
-                floatingContentCoordinator, pipPerfHintControllerOptional);
+                floatingContentCoordinator, pipScheduler, pipPerfHintControllerOptional,
+                pipBoundsAlgorithm, pipTransitionState);
     }
 
     @WMSingleton
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 a10c7c0..0b7a3e8 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
@@ -20,6 +20,7 @@
 import android.app.ActivityTaskManager.INVALID_TASK_ID
 import android.app.TaskInfo
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.content.Context
 import android.os.IBinder
 import android.util.SparseArray
 import android.view.SurfaceControl
@@ -38,6 +39,7 @@
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.DesktopModeStatus
 import com.android.wm.shell.shared.TransitionUtil
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
@@ -49,6 +51,7 @@
  * and other transitions that originate both within and outside shell.
  */
 class DesktopModeLoggerTransitionObserver(
+    context: Context,
     shellInit: ShellInit,
     private val transitions: Transitions,
     private val desktopModeEventLogger: DesktopModeEventLogger
@@ -57,7 +60,8 @@
     private val idSequence: InstanceIdSequence by lazy { InstanceIdSequence(Int.MAX_VALUE) }
 
     init {
-        if (Transitions.ENABLE_SHELL_TRANSITIONS && DesktopModeStatus.isEnabled()) {
+        if (Transitions.ENABLE_SHELL_TRANSITIONS &&
+            DesktopModeStatus.canEnterDesktopMode(context)) {
             shellInit.addInitCallback(this::onInit, this)
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 2d508b2..7e0234e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -48,7 +48,6 @@
         val activeTasks: ArraySet<Int> = ArraySet(),
         val visibleTasks: ArraySet<Int> = ArraySet(),
         val minimizedTasks: ArraySet<Int> = ArraySet(),
-        var stashed: Boolean = false
     )
 
     // Token of the current wallpaper activity, used to remove it when the last task is removed
@@ -95,10 +94,8 @@
         visibleTasksListeners[visibleTasksListener] = executor
         displayData.keyIterator().forEach { displayId ->
             val visibleTasksCount = getVisibleTaskCount(displayId)
-            val stashed = isStashed(displayId)
             executor.execute {
                 visibleTasksListener.onTasksVisibilityChanged(displayId, visibleTasksCount)
-                visibleTasksListener.onStashedChanged(displayId, stashed)
             }
         }
     }
@@ -400,26 +397,6 @@
     }
 
     /**
-     * Update stashed status on display with id [displayId]
-     */
-    fun setStashed(displayId: Int, stashed: Boolean) {
-        val data = displayData.getOrCreate(displayId)
-        val oldValue = data.stashed
-        data.stashed = stashed
-        if (oldValue != stashed) {
-            KtProtoLog.d(
-                    WM_SHELL_DESKTOP_MODE,
-                    "DesktopTaskRepo: mark stashed=%b displayId=%d",
-                    stashed,
-                    displayId
-            )
-            visibleTasksListeners.forEach { (listener, executor) ->
-                executor.execute { listener.onStashedChanged(displayId, stashed) }
-            }
-        }
-    }
-
-    /**
      * Removes and returns the bounds saved before maximizing the given task.
      */
     fun removeBoundsBeforeMaximize(taskId: Int): Rect? {
@@ -433,13 +410,6 @@
         boundsBeforeMaximizeByTaskId.set(taskId, Rect(bounds))
     }
 
-    /**
-     * Check if display with id [displayId] has desktop tasks stashed
-     */
-    fun isStashed(displayId: Int): Boolean {
-        return displayData[displayId]?.stashed ?: false
-    }
-
     internal fun dump(pw: PrintWriter, prefix: String) {
         val innerPrefix = "$prefix  "
         pw.println("${prefix}DesktopModeTaskRepository")
@@ -455,7 +425,6 @@
             pw.println("${prefix}Display $displayId:")
             pw.println("${innerPrefix}activeTasks=${data.activeTasks.toDumpString()}")
             pw.println("${innerPrefix}visibleTasks=${data.visibleTasks.toDumpString()}")
-            pw.println("${innerPrefix}stashed=${data.stashed}")
         }
     }
 
@@ -477,11 +446,6 @@
          * Called when the desktop changes the number of visible freeform tasks.
          */
         fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {}
-
-        /**
-         * Called when the desktop stashed status changes.
-         */
-        fun onStashedChanged(displayId: Int, stashed: Boolean) {}
     }
 }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
new file mode 100644
index 0000000..aa11a7d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.desktopmode
+
+import android.util.Log
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.InstanceIdSequence
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.wm.shell.dagger.WMSingleton
+import javax.inject.Inject
+
+/**
+ * Log Aster UIEvents for desktop windowing mode.
+ */
+@WMSingleton
+class DesktopModeUiEventLogger @Inject constructor(
+    private val mUiEventLogger: UiEventLogger,
+    private val mInstanceIdSequence: InstanceIdSequence
+) {
+    /**
+     * Logs an event for a CUI, on a particular package.
+     *
+     * @param uid The user id associated with the package the user is interacting with
+     * @param packageName The name of the package the user is interacting with
+     * @param event The event type to generate
+     */
+    fun log(uid: Int, packageName: String, event: DesktopUiEventEnum) {
+        if (packageName.isEmpty() || uid < 0) {
+            Log.d(TAG, "Skip logging since package name is empty or bad uid")
+            return
+        }
+        mUiEventLogger.log(event, uid, packageName)
+    }
+
+    /**
+     * Retrieves a new instance id for a new interaction.
+     */
+    fun getNewInstanceId(): InstanceId = mInstanceIdSequence.newInstanceId()
+
+    /**
+     * Logs an event as part of a particular CUI, on a particular package.
+     *
+     * @param instanceId The id identifying an interaction, potentially taking place across multiple
+     * surfaces. There should be a new id generated for each distinct CUI.
+     * @param uid The user id associated with the package the user is interacting with
+     * @param packageName The name of the package the user is interacting with
+     * @param event The event type to generate
+     */
+    fun logWithInstanceId(
+        instanceId: InstanceId,
+        uid: Int,
+        packageName: String,
+        event: DesktopUiEventEnum
+    ) {
+        if (packageName.isEmpty() || uid < 0) {
+            Log.d(TAG, "Skip logging since package name is empty or bad uid")
+            return
+        }
+        mUiEventLogger.logWithInstanceId(event, uid, packageName, instanceId)
+    }
+
+    companion object {
+        /**
+         * Enums for logging desktop windowing mode UiEvents.
+         */
+        enum class DesktopUiEventEnum(private val mId: Int) : UiEventLogger.UiEventEnum {
+
+            @UiEvent(doc = "Resize the window in desktop windowing mode by dragging the edge")
+            DESKTOP_WINDOW_EDGE_DRAG_RESIZE(1721),
+
+            @UiEvent(doc = "Resize the window in desktop windowing mode by dragging the corner")
+            DESKTOP_WINDOW_CORNER_DRAG_RESIZE(1722),
+
+            @UiEvent(doc = "Tap on the window header maximize button in desktop windowing mode")
+            DESKTOP_WINDOW_MAXIMIZE_BUTTON_TAP(1723),
+
+            @UiEvent(doc = "Double tap on window header to maximize it in desktop windowing mode")
+            DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_MAXIMIZE(1724);
+
+            override fun getId(): Int = mId
+        }
+
+        private const val TAG = "DesktopModeUiEventLogger"
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
new file mode 100644
index 0000000..6da3741
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -0,0 +1,173 @@
+/*
+ * 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:JvmName("DesktopModeUtils")
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.pm.ActivityInfo.isFixedOrientationLandscape
+import android.content.pm.ActivityInfo.isFixedOrientationPortrait
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.content.res.Configuration.ORIENTATION_PORTRAIT
+import android.graphics.Rect
+import android.os.SystemProperties
+import android.util.Size
+import com.android.wm.shell.common.DisplayLayout
+
+
+val DESKTOP_MODE_INITIAL_BOUNDS_SCALE: Float = SystemProperties
+        .getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f
+
+val DESKTOP_MODE_LANDSCAPE_APP_PADDING: Int = SystemProperties
+        .getInt("persist.wm.debug.desktop_mode_landscape_app_padding", 25)
+
+
+/**
+ * Calculates the initial bounds required for an application to fill a scale of the display bounds
+ * without any letterboxing. This is done by taking into account the applications fullscreen size,
+ * aspect ratio, orientation and resizability to calculate an area this is compatible with the
+ * applications previous configuration.
+ */
+fun calculateInitialBounds(
+    displayLayout: DisplayLayout,
+    taskInfo: RunningTaskInfo,
+    scale: Float = DESKTOP_MODE_INITIAL_BOUNDS_SCALE
+): Rect {
+    val screenBounds = Rect(0, 0, displayLayout.width(), displayLayout.height())
+    val appAspectRatio = calculateAspectRatio(taskInfo)
+    val idealSize = calculateIdealSize(screenBounds, scale)
+    // If no top activity exists, apps fullscreen bounds and aspect ratio cannot be calculated.
+    // Instead default to the desired initial bounds.
+    val topActivityInfo = taskInfo.topActivityInfo
+        ?: return positionInScreen(idealSize, screenBounds)
+
+    val initialSize: Size = when (taskInfo.configuration.orientation) {
+        ORIENTATION_LANDSCAPE -> {
+            if (taskInfo.isResizeable) {
+                if (isFixedOrientationPortrait(topActivityInfo.screenOrientation)) {
+                    // Respect apps fullscreen width
+                    Size(taskInfo.appCompatTaskInfo.topActivityLetterboxWidth, idealSize.height)
+                } else {
+                    idealSize
+                }
+            } else {
+                maximumSizeMaintainingAspectRatio(taskInfo, idealSize,
+                    appAspectRatio)
+            }
+        }
+        ORIENTATION_PORTRAIT -> {
+            val customPortraitWidthForLandscapeApp = screenBounds.width() -
+                    (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2)
+            if (taskInfo.isResizeable) {
+                if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
+                    // Respect apps fullscreen height and apply custom app width
+                    Size(customPortraitWidthForLandscapeApp,
+                        taskInfo.appCompatTaskInfo.topActivityLetterboxHeight)
+                } else {
+                    idealSize
+                }
+            } else {
+                if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
+                    // Apply custom app width and calculate maximum size
+                    maximumSizeMaintainingAspectRatio(
+                        taskInfo,
+                        Size(customPortraitWidthForLandscapeApp, idealSize.height),
+                        appAspectRatio)
+                } else {
+                    maximumSizeMaintainingAspectRatio(taskInfo, idealSize,
+                        appAspectRatio)
+                }
+            }
+        }
+        else -> {
+            idealSize
+        }
+    }
+
+    return positionInScreen(initialSize, screenBounds)
+}
+
+/**
+ * Calculates the largest size that can fit in a given area while maintaining a specific aspect
+ * ratio.
+ */
+private fun maximumSizeMaintainingAspectRatio(
+    taskInfo: RunningTaskInfo,
+    targetArea: Size,
+    aspectRatio: Float
+): Size {
+    val targetHeight = targetArea.height
+    val targetWidth = targetArea.width
+    val finalHeight: Int
+    val finalWidth: Int
+    if (isFixedOrientationPortrait(taskInfo.topActivityInfo!!.screenOrientation)) {
+        val tempWidth = (targetHeight / aspectRatio).toInt()
+        if (tempWidth <= targetWidth) {
+            finalHeight = targetHeight
+            finalWidth = tempWidth
+        } else {
+            finalWidth = targetWidth
+            finalHeight = (finalWidth * aspectRatio).toInt()
+        }
+    } else {
+        val tempWidth = (targetHeight * aspectRatio).toInt()
+        if (tempWidth <= targetWidth) {
+            finalHeight = targetHeight
+            finalWidth = tempWidth
+        } else {
+            finalWidth = targetWidth
+            finalHeight = (finalWidth / aspectRatio).toInt()
+        }
+    }
+    return Size(finalWidth, finalHeight)
+}
+
+/**
+ * Calculates the aspect ratio of an activity from its fullscreen bounds.
+ */
+private fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float {
+    if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) {
+        val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxWidth
+        val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxHeight
+        return maxOf(appLetterboxWidth, appLetterboxHeight) /
+                minOf(appLetterboxWidth, appLetterboxHeight).toFloat()
+    }
+    val appBounds = taskInfo.configuration.windowConfiguration.appBounds ?: return 1f
+    return maxOf(appBounds.height(), appBounds.width()) /
+                minOf(appBounds.height(), appBounds.width()).toFloat()
+}
+
+/**
+ * Calculates the desired initial bounds for applications in desktop windowing. This is done as a
+ * scale of the screen bounds.
+ */
+private fun calculateIdealSize(screenBounds: Rect, scale: Float): Size {
+    val width = (screenBounds.width() * scale).toInt()
+    val height = (screenBounds.height() * scale).toInt()
+    return Size(width, height)
+}
+
+/**
+ * Adjusts bounds to be positioned in the middle of the screen.
+ */
+private fun positionInScreen(desiredSize: Size, screenBounds: Rect): Rect {
+    // TODO(b/325240051): Position apps with bottom heavy offset
+    val heightOffset = (screenBounds.height() - desiredSize.height) / 2
+    val widthOffset = (screenBounds.width() - desiredSize.width) / 2
+    return Rect(widthOffset, heightOffset,
+        desiredSize.width + widthOffset, desiredSize.height + heightOffset)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 1c690f5..79a2090 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -47,6 +47,7 @@
 import android.window.TransitionRequestInfo
 import android.window.WindowContainerTransaction
 import androidx.annotation.BinderThread
+import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.policy.ScreenDecorationsUtils
 import com.android.window.flags.Flags
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -71,6 +72,7 @@
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
 import com.android.wm.shell.recents.RecentsTransitionHandler
 import com.android.wm.shell.recents.RecentsTransitionStateListener
+import com.android.wm.shell.shared.DesktopModeStatus
 import com.android.wm.shell.shared.annotations.ExternalThread
 import com.android.wm.shell.shared.annotations.ShellMainThread
 import com.android.wm.shell.splitscreen.SplitScreenController
@@ -85,7 +87,6 @@
 import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility
 import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
 import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
-import com.android.wm.shell.windowdecor.extension.isFreeform
 import com.android.wm.shell.windowdecor.extension.isFullscreen
 import java.io.PrintWriter
 import java.util.Optional
@@ -167,7 +168,7 @@
 
     init {
         desktopMode = DesktopModeImpl()
-        if (DesktopModeStatus.isEnabled()) {
+        if (DesktopModeStatus.canEnterDesktopMode(context)) {
             shellInit.addInitCallback({ onInit() }, this)
         }
     }
@@ -203,6 +204,11 @@
         dragAndDropController.addListener(this)
     }
 
+    @VisibleForTesting
+    fun getVisualIndicator(): DesktopModeVisualIndicator? {
+        return visualIndicator
+    }
+
     fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
         toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
         enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
@@ -222,7 +228,7 @@
         bringDesktopAppsToFront(displayId, wct)
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            // TODO(b/255649902): ensure remote transition is supplied once state is introduced
+            // TODO(b/309014605): ensure remote transition is supplied once state is introduced
             val transitionType = if (remoteTransition == null) TRANSIT_NONE else TRANSIT_TO_FRONT
             val handler = remoteTransition?.let {
                 OneShotRemoteHandler(transitions.mainExecutor, remoteTransition)
@@ -235,34 +241,6 @@
         }
     }
 
-    /**
-     * Stash desktop tasks on display with id [displayId].
-     *
-     * When desktop tasks are stashed, launcher home screen icons are fully visible. New apps
-     * launched in this state will be added to the desktop. Existing desktop tasks will be brought
-     * back to front during the launch.
-     */
-    fun stashDesktopApps(displayId: Int) {
-        if (DesktopModeStatus.isStashingEnabled()) {
-            KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: stashDesktopApps")
-            desktopModeTaskRepository.setStashed(displayId, true)
-        }
-    }
-
-    /**
-     * Clear the stashed state for the given display
-     */
-    fun hideStashedDesktopApps(displayId: Int) {
-        if (DesktopModeStatus.isStashingEnabled()) {
-            KtProtoLog.v(
-                    WM_SHELL_DESKTOP_MODE,
-                    "DesktopTasksController: hideStashedApps displayId=%d",
-                    displayId
-            )
-            desktopModeTaskRepository.setStashed(displayId, false)
-        }
-    }
-
     /** Get number of tasks that are marked as visible */
     fun getVisibleTaskCount(displayId: Int): Int {
         return desktopModeTaskRepository.getVisibleTaskCount(displayId)
@@ -605,8 +583,9 @@
     }
 
     /**
-     * Quick-resizes a desktop task, toggling between the stable bounds and the last saved bounds
-     * if available or the default bounds otherwise.
+     * Quick-resizes a desktop task, toggling between a fullscreen state (represented by the
+     * stable bounds) and a free floating state (either the last saved bounds if available or the
+     * default bounds otherwise).
      */
     fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo) {
         val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
@@ -623,7 +602,11 @@
             if (taskBoundsBeforeMaximize != null) {
                 destinationBounds.set(taskBoundsBeforeMaximize)
             } else {
-                destinationBounds.set(getDefaultDesktopTaskBounds(displayLayout))
+                if (Flags.enableWindowingDynamicInitialBounds()){
+                    destinationBounds.set(calculateInitialBounds(displayLayout, taskInfo))
+                } else {
+                    destinationBounds.set(getDefaultDesktopTaskBounds(displayLayout))
+                }
             }
         } else {
             // Save current bounds so that task can be restored back to original bounds if necessary
@@ -861,8 +844,6 @@
         val result = triggerTask?.let { task ->
             when {
                 request.type == TRANSIT_TO_BACK -> handleBackNavigation(task)
-                // If display has tasks stashed, handle as stashed launch
-                task.isStashed -> handleStashedTaskLaunch(task, transition)
                 // Check if the task has a top transparent activity
                 shouldLaunchAsModal(task) -> handleTransparentTaskLaunch(task)
                 // Check if fullscreen task should be updated
@@ -901,12 +882,8 @@
                 .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
     }
 
-    private val TaskInfo.isStashed: Boolean
-        get() = desktopModeTaskRepository.isStashed(displayId)
-
-    private fun shouldLaunchAsModal(task: TaskInfo): Boolean {
-        return Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)
-    }
+    private fun shouldLaunchAsModal(task: TaskInfo) =
+        Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)
 
     private fun shouldRemoveWallpaper(request: TransitionRequestInfo): Boolean {
         return Flags.enableDesktopWindowingWallpaperActivity() &&
@@ -967,24 +944,6 @@
         return null
     }
 
-    private fun handleStashedTaskLaunch(
-            task: RunningTaskInfo,
-            transition: IBinder
-    ): WindowContainerTransaction {
-        KtProtoLog.d(
-                WM_SHELL_DESKTOP_MODE,
-                "DesktopTasksController: launch apps with stashed on transition taskId=%d",
-                task.taskId
-        )
-        val wct = WindowContainerTransaction()
-        val taskToMinimize =
-                bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
-        addMoveToDesktopChanges(wct, task)
-        desktopModeTaskRepository.setStashed(task.displayId, false)
-        addPendingMinimizeTransition(transition, taskToMinimize)
-        return wct
-    }
-
     // Always launch transparent tasks in fullscreen.
     private fun handleTransparentTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
         // Already fullscreen, no-op.
@@ -1012,6 +971,7 @@
         wct: WindowContainerTransaction,
         taskInfo: RunningTaskInfo
     ) {
+        val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
         val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!!
         val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
         val targetWindowingMode = if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) {
@@ -1020,6 +980,9 @@
         } else {
             WINDOWING_MODE_FREEFORM
         }
+        if (Flags.enableWindowingDynamicInitialBounds()) {
+            wct.setBounds(taskInfo.token, calculateInitialBounds(displayLayout, taskInfo))
+        }
         wct.setWindowingMode(taskInfo.token, targetWindowingMode)
         wct.reorder(taskInfo.token, true /* onTop */)
         if (isDesktopDensityOverrideSet()) {
@@ -1240,13 +1203,17 @@
      * @param y height of drag, to be checked against status bar height.
      */
     fun onDragPositioningEndThroughStatusBar(inputCoordinates: PointF, taskInfo: RunningTaskInfo) {
-        val indicator = visualIndicator ?: return
+        val indicator = getVisualIndicator() ?: return
         val indicatorType = indicator
             .updateIndicatorType(inputCoordinates, taskInfo.windowingMode)
         when (indicatorType) {
             DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
                 val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
-                finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout))
+                if (Flags.enableWindowingDynamicInitialBounds()) {
+                    finalizeDragToDesktop(taskInfo, calculateInitialBounds(displayLayout, taskInfo))
+                } else {
+                    finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout))
+                }
             }
             DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
             DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
@@ -1409,16 +1376,6 @@
                     l -> l.onTasksVisibilityChanged(displayId, visibleTasksCount)
                 }
             }
-
-            override fun onStashedChanged(displayId: Int, stashed: Boolean) {
-                KtProtoLog.v(
-                        WM_SHELL_DESKTOP_MODE,
-                        "IDesktopModeImpl: onStashedChanged display=%d stashed=%b",
-                        displayId,
-                        stashed
-                )
-                remoteListener.call { l -> l.onStashedChanged(displayId, stashed) }
-            }
         }
 
         init {
@@ -1450,20 +1407,6 @@
             ) { c -> c.showDesktopApps(displayId, remoteTransition) }
         }
 
-        override fun stashDesktopApps(displayId: Int) {
-            ExecutorUtils.executeRemoteCallWithTaskPermission(
-                    controller,
-                    "stashDesktopApps"
-            ) { c -> c.stashDesktopApps(displayId) }
-        }
-
-        override fun hideStashedDesktopApps(displayId: Int) {
-            ExecutorUtils.executeRemoteCallWithTaskPermission(
-                    controller,
-                    "hideStashedDesktopApps"
-            ) { c -> c.hideStashedDesktopApps(displayId) }
-        }
-
         override fun showDesktopApp(taskId: Int) {
             ExecutorUtils.executeRemoteCallWithTaskPermission(
                     controller,
@@ -1471,6 +1414,20 @@
             ) { c -> c.moveTaskToFront(taskId) }
         }
 
+        override fun stashDesktopApps(displayId: Int) {
+            KtProtoLog.w(
+                WM_SHELL_DESKTOP_MODE,
+                "IDesktopModeImpl: stashDesktopApps is deprecated"
+            )
+        }
+
+        override fun hideStashedDesktopApps(displayId: Int) {
+            KtProtoLog.w(
+                WM_SHELL_DESKTOP_MODE,
+                "IDesktopModeImpl: hideStashedDesktopApps is deprecated"
+            )
+        }
+
         override fun getVisibleTaskCount(displayId: Int): Int {
             val result = IntArray(1)
             ExecutorUtils.executeRemoteCallWithTaskPermission(
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 3404d37..0f88384 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
@@ -25,6 +25,7 @@
 import androidx.annotation.VisibleForTesting
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.shared.DesktopModeStatus
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.TransitionObserver
 import com.android.wm.shell.util.KtProtoLog
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index 20df264..dae75f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -16,12 +16,14 @@
 
 package com.android.wm.shell.desktopmode
 
+import android.content.Context
 import android.os.IBinder
 import android.view.SurfaceControl
 import android.view.WindowManager
 import android.window.TransitionInfo
 import com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.DesktopModeStatus
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.util.KtProtoLog
@@ -33,13 +35,15 @@
  * and other transitions that originate both within and outside shell.
  */
 class DesktopTasksTransitionObserver(
+    context: Context,
     private val desktopModeTaskRepository: DesktopModeTaskRepository,
     private val transitions: Transitions,
     shellInit: ShellInit
 ) : Transitions.TransitionObserver {
 
     init {
-        if (Transitions.ENABLE_SHELL_TRANSITIONS && DesktopModeStatus.isEnabled()) {
+        if (Transitions.ENABLE_SHELL_TRANSITIONS &&
+            DesktopModeStatus.canEnterDesktopMode(context)) {
             shellInit.addInitCallback(::onInit, this)
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index fa43522..c36f8de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -28,10 +28,10 @@
     /** Show apps on the desktop on the given display */
     void showDesktopApps(int displayId, in RemoteTransition remoteTransition);
 
-    /** Stash apps on the desktop to allow launching another app from home screen */
+    /** @deprecated use {@link #showDesktopApps} instead. */
     void stashDesktopApps(int displayId);
 
-    /** Hide apps that may be stashed */
+    /** @deprecated this is no longer supported. */
     void hideStashedDesktopApps(int displayId);
 
     /** Bring task with the given id to front */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
index 8ed87f2..8ebdfdc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
@@ -25,6 +25,6 @@
     /** Desktop tasks visibility has changed. Visible if at least 1 task is visible. */
     oneway void onTasksVisibilityChanged(int displayId, int visibleTasksCount);
 
-    /** Desktop task stashed status has changed. */
+    /** @deprecated this is no longer supported. */
     oneway void onStashedChanged(int displayId, boolean stashed);
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index ecb53dc..4bb10df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -16,17 +16,17 @@
 
 package com.android.wm.shell.draganddrop;
 
-import static android.app.StatusBarManager.DISABLE2_NONE;
 import static android.app.StatusBarManager.DISABLE_NONE;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
 import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitScreenUtils.getResizingBackgroundColor;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
@@ -41,7 +41,6 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.Color;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -278,7 +277,7 @@
                 final int activityType = taskInfo1.getActivityType();
                 if (activityType == ACTIVITY_TYPE_STANDARD) {
                     Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo);
-                    int bgColor1 = getResizingBackgroundColor(taskInfo1);
+                    int bgColor1 = getResizingBackgroundColor(taskInfo1).toArgb();
                     mDropZoneView1.setAppInfo(bgColor1, icon1);
                     mDropZoneView2.setAppInfo(bgColor1, icon1);
                     updateDropZoneSizes(null, null); // passing null splits the views evenly
@@ -298,10 +297,10 @@
                     mSplitScreenController.getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
             if (topOrLeftTask != null && bottomOrRightTask != null) {
                 Drawable topOrLeftIcon = mIconProvider.getIcon(topOrLeftTask.topActivityInfo);
-                int topOrLeftColor = getResizingBackgroundColor(topOrLeftTask);
+                int topOrLeftColor = getResizingBackgroundColor(topOrLeftTask).toArgb();
                 Drawable bottomOrRightIcon = mIconProvider.getIcon(
                         bottomOrRightTask.topActivityInfo);
-                int bottomOrRightColor = getResizingBackgroundColor(bottomOrRightTask);
+                int bottomOrRightColor = getResizingBackgroundColor(bottomOrRightTask).toArgb();
                 mDropZoneView1.setAppInfo(topOrLeftColor, topOrLeftIcon);
                 mDropZoneView2.setAppInfo(bottomOrRightColor, bottomOrRightIcon);
             }
@@ -516,20 +515,18 @@
     }
 
     private void animateFullscreenContainer(boolean visible) {
-        int flags = visible ? HIDE_STATUS_BAR_FLAGS : DISABLE_NONE;
-        StatusBarManager.DisableInfo disableInfo = new StatusBarManager.DisableInfo(flags,
-                DISABLE2_NONE);
-        mStatusBarManager.requestDisabledComponent(disableInfo, "animateFullscreenContainer");
+        mStatusBarManager.disable(visible
+                ? HIDE_STATUS_BAR_FLAGS
+                : DISABLE_NONE);
         // We're only using the first drop zone if there is one fullscreen target
         mDropZoneView1.setShowingMargin(visible);
         mDropZoneView1.setShowingHighlight(visible);
     }
 
     private void animateSplitContainers(boolean visible, Runnable animCompleteCallback) {
-        int flags = visible ? HIDE_STATUS_BAR_FLAGS : DISABLE_NONE;
-        StatusBarManager.DisableInfo disableInfo = new StatusBarManager.DisableInfo(flags,
-                DISABLE2_NONE);
-        mStatusBarManager.requestDisabledComponent(disableInfo, "animateSplitContainers");
+        mStatusBarManager.disable(visible
+                ? HIDE_STATUS_BAR_FLAGS
+                : DISABLE_NONE);
         mDropZoneView1.setShowingMargin(visible);
         mDropZoneView2.setShowingMargin(visible);
         Animator animator = mDropZoneView1.getAnimator();
@@ -558,11 +555,6 @@
         }
     }
 
-    private static int getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) {
-        final int taskBgColor = taskInfo.taskDescription.getBackgroundColor();
-        return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb();
-    }
-
     /**
      * Dumps information about this drag layout.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 6fea203..e0e2e706 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -21,14 +21,15 @@
 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM;
 
 import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
 import android.util.SparseArray;
 import android.view.SurfaceControl;
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -44,6 +45,7 @@
         ShellTaskOrganizer.FocusListener {
     private static final String TAG = "FreeformTaskListener";
 
+    private final Context mContext;
     private final ShellTaskOrganizer mShellTaskOrganizer;
     private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
     private final WindowDecorViewModel mWindowDecorationViewModel;
@@ -56,10 +58,12 @@
     }
 
     public FreeformTaskListener(
+            Context context,
             ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
             Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             WindowDecorViewModel windowDecorationViewModel) {
+        mContext = context;
         mShellTaskOrganizer = shellTaskOrganizer;
         mWindowDecorationViewModel = windowDecorationViewModel;
         mDesktopModeTaskRepository = desktopModeTaskRepository;
@@ -70,7 +74,7 @@
 
     private void onInit() {
         mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FREEFORM);
-        if (DesktopModeStatus.isEnabled()) {
+        if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
             mShellTaskOrganizer.addFocusListener(this);
         }
     }
@@ -92,7 +96,7 @@
             t.apply();
         }
 
-        if (DesktopModeStatus.isEnabled()) {
+        if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
             mDesktopModeTaskRepository.ifPresent(repository -> {
                 repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
                 repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId);
@@ -114,7 +118,7 @@
                 taskInfo.taskId);
         mTasks.remove(taskInfo.taskId);
 
-        if (DesktopModeStatus.isEnabled()) {
+        if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
             mDesktopModeTaskRepository.ifPresent(repository -> {
                 repository.removeFreeformTask(taskInfo.taskId);
                 repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId);
@@ -125,7 +129,7 @@
                 repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId, false);
             });
         }
-
+        mWindowDecorationViewModel.onTaskVanished(taskInfo);
         if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
             mWindowDecorationViewModel.destroyWindowDecoration(taskInfo);
         }
@@ -139,7 +143,7 @@
                 taskInfo.taskId);
         mWindowDecorationViewModel.onTaskInfoChanged(taskInfo);
         state.mTaskInfo = taskInfo;
-        if (DesktopModeStatus.isEnabled()) {
+        if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
             mDesktopModeTaskRepository.ifPresent(repository -> {
                 if (taskInfo.isVisible) {
                     if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) {
@@ -161,7 +165,7 @@
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG,
                 "Freeform Task Focus Changed: #%d focused=%b",
                 taskInfo.taskId, taskInfo.isFocused);
-        if (DesktopModeStatus.isEnabled() && taskInfo.isFocused) {
+        if (DesktopModeStatus.canEnterDesktopMode(mContext) && taskInfo.isFocused) {
             mDesktopModeTaskRepository.ifPresent(repository -> {
                 repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
                 repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 998728d6..2626e73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -161,7 +161,7 @@
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d",
                 taskInfo.taskId);
         mTasks.remove(taskInfo.taskId);
-
+        mWindowDecorViewModelOptional.ifPresent(v -> v.onTaskVanished(taskInfo));
         if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
         if (mWindowDecorViewModelOptional.isPresent()) {
             mWindowDecorViewModelOptional.get().destroyWindowDecoration(taskInfo);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index 9eaf7e4..c79eef7e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -22,6 +22,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.service.dreams.Flags.dismissDreamOnKeyguardDismiss;
 import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING;
@@ -83,6 +84,7 @@
      * @see KeyguardTransitions
      */
     private IRemoteTransition mExitTransition = null;
+    private IRemoteTransition mAppearTransition = null;
     private IRemoteTransition mOccludeTransition = null;
     private IRemoteTransition mOccludeByDreamTransition = null;
     private IRemoteTransition mUnoccludeTransition = null;
@@ -170,26 +172,28 @@
 
         // Choose a transition applicable for the changes and keyguard state.
         if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) {
-            return startAnimation(mExitTransition,
-                    "going-away",
+            return startAnimation(mExitTransition, "going-away",
                     transition, info, startTransaction, finishTransaction, finishCallback);
         }
 
+        if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0) {
+            return startAnimation(mAppearTransition, "appearing",
+                    transition, info, startTransaction, finishTransaction, finishCallback);
+        }
+
+
         // Occlude/unocclude animations are only played if the keyguard is locked.
         if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
             if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0) {
                 if (hasOpeningDream(info)) {
-                    return startAnimation(mOccludeByDreamTransition,
-                            "occlude-by-dream",
+                    return startAnimation(mOccludeByDreamTransition, "occlude-by-dream",
                             transition, info, startTransaction, finishTransaction, finishCallback);
                 } else {
-                    return startAnimation(mOccludeTransition,
-                            "occlude",
+                    return startAnimation(mOccludeTransition, "occlude",
                             transition, info, startTransaction, finishTransaction, finishCallback);
                 }
             } else if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
-                return startAnimation(mUnoccludeTransition,
-                        "unocclude",
+                return startAnimation(mUnoccludeTransition, "unocclude",
                         transition, info, startTransaction, finishTransaction, finishCallback);
             }
         }
@@ -359,11 +363,13 @@
         @Override
         public void register(
                 IRemoteTransition exitTransition,
+                IRemoteTransition appearTransition,
                 IRemoteTransition occludeTransition,
                 IRemoteTransition occludeByDreamTransition,
                 IRemoteTransition unoccludeTransition) {
             mMainExecutor.execute(() -> {
                 mExitTransition = exitTransition;
+                mAppearTransition = appearTransition;
                 mOccludeTransition = occludeTransition;
                 mOccludeByDreamTransition = occludeByDreamTransition;
                 mUnoccludeTransition = unoccludeTransition;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
index 4215b2c..b7245b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
@@ -35,6 +35,7 @@
      */
     default void register(
             @NonNull IRemoteTransition unlockTransition,
+            @NonNull IRemoteTransition appearTransition,
             @NonNull IRemoteTransition occludeTransition,
             @NonNull IRemoteTransition occludeByDreamTransition,
             @NonNull IRemoteTransition unoccludeTransition) {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index 7b1ef5c..a749019 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -39,7 +39,7 @@
      * @param isSysUiStateValid Is SysUI state valid or not.
      * @param flag Current SysUI state.
      */
-    default void onSystemUiStateChanged(boolean isSysUiStateValid, int flag) {
+    default void onSystemUiStateChanged(boolean isSysUiStateValid, long flag) {
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index e885262..e1657f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -854,7 +854,8 @@
         mPipUiEventLoggerLogger.log(uiEventEnum);
 
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "onTaskAppeared: %s, state=%s", mTaskInfo.topActivity, mPipTransitionState);
+                "onTaskAppeared: %s, state=%s, taskId=%s", mTaskInfo.topActivity,
+                mPipTransitionState, mTaskInfo.taskId);
         if (mPipTransitionState.getInSwipePipToHomeTransition()) {
             if (!mWaitForFixedRotation) {
                 onEndOfSwipePipToHomeTransition();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index fdde3ee..2082756 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -824,12 +824,10 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback,
             @NonNull TaskInfo taskInfo) {
         startTransaction.apply();
-        if (info.getChanges().isEmpty()) {
+        final TransitionInfo.Change pipChange = findCurrentPipTaskChange(info);
+        if (pipChange == null) {
             ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "removePipImmediately is called with empty changes");
-        } else {
-            finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(),
-                    mPipDisplayLayoutState.getDisplayBounds());
+                    "removePipImmediately is called without pip change");
         }
         mPipOrganizer.onExitPipFinished(taskInfo);
         finishCallback.onTransitionFinished(null);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 139cde2..85f9194 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -847,7 +847,7 @@
         }
     }
 
-    private void onSystemUiStateChanged(boolean isValidState, int flag) {
+    private void onSystemUiStateChanged(boolean isValidState, long flag) {
         mTouchHandler.onSystemUiStateChanged(isValidState);
     }
 
@@ -1195,7 +1195,7 @@
         }
 
         @Override
-        public void onSystemUiStateChanged(boolean isSysUiStateValid, int flag) {
+        public void onSystemUiStateChanged(boolean isSysUiStateValid, long flag) {
             mMainExecutor.execute(() -> {
                 PipController.this.onSystemUiStateChanged(isSysUiStateValid, flag);
             });
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java
index 03547a5..b757b00 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java
@@ -53,7 +53,7 @@
      * Listener interface for callers to learn when this class is registered or unregistered with
      * window manager
      */
-    private interface RegistrationListener {
+    interface RegistrationListener {
         void onRegistrationChanged(boolean isRegistered);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index 619bed4..be10151 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -31,7 +31,9 @@
 import android.content.Context;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.os.Debug;
+import android.view.SurfaceControl;
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
@@ -39,6 +41,7 @@
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.common.pip.PipPerfHintController;
 import com.android.wm.shell.common.pip.PipSnapAlgorithm;
@@ -55,8 +58,10 @@
  * A helper to animate and manipulate the PiP.
  */
 public class PipMotionHelper implements PipAppOpsListener.Callback,
-        FloatingContentCoordinator.FloatingContent {
+        FloatingContentCoordinator.FloatingContent,
+        PipTransitionState.PipTransitionStateChangedListener {
     private static final String TAG = "PipMotionHelper";
+    private static final String FLING_BOUNDS_CHANGE = "fling_bounds_change";
     private static final boolean DEBUG = false;
 
     private static final int SHRINK_STACK_FROM_MENU_DURATION = 250;
@@ -72,7 +77,9 @@
 
     private final Context mContext;
     private @NonNull PipBoundsState mPipBoundsState;
-
+    private @NonNull PipBoundsAlgorithm mPipBoundsAlgorithm;
+    private @NonNull PipScheduler mPipScheduler;
+    private @NonNull PipTransitionState mPipTransitionState;
     private PhonePipMenuController mMenuController;
     private PipSnapAlgorithm mSnapAlgorithm;
 
@@ -145,6 +152,11 @@
     private boolean mDismissalPending = false;
 
     /**
+     * Set to true if bounds change transition has been scheduled from PipMotionHelper.
+     */
+    private boolean mWaitingForBoundsChangeTransition = false;
+
+    /**
      * Gets set in {@link #animateToExpandedState(Rect, Rect, Rect, Runnable)}, this callback is
      * used to show menu activity when the expand animation is completed.
      */
@@ -152,22 +164,25 @@
 
     public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
             PhonePipMenuController menuController, PipSnapAlgorithm snapAlgorithm,
-            FloatingContentCoordinator floatingContentCoordinator,
-            Optional<PipPerfHintController> pipPerfHintControllerOptional) {
+            FloatingContentCoordinator floatingContentCoordinator, PipScheduler pipScheduler,
+            Optional<PipPerfHintController> pipPerfHintControllerOptional,
+            PipBoundsAlgorithm pipBoundsAlgorithm, PipTransitionState pipTransitionState) {
         mContext = context;
         mPipBoundsState = pipBoundsState;
+        mPipBoundsAlgorithm = pipBoundsAlgorithm;
+        mPipScheduler = pipScheduler;
         mMenuController = menuController;
         mSnapAlgorithm = snapAlgorithm;
         mFloatingContentCoordinator = floatingContentCoordinator;
         mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
         mResizePipUpdateListener = (target, values) -> {
             if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
-                /*
-                mPipTaskOrganizer.scheduleUserResizePip(getBounds(),
-                        mPipBoundsState.getMotionBoundsState().getBoundsInMotion(), null);
-                 */
+                mPipScheduler.scheduleUserResizePip(
+                        mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
             }
         };
+        mPipTransitionState = pipTransitionState;
+        mPipTransitionState.addPipTransitionStateChangedListener(this);
     }
 
     void init() {
@@ -236,12 +251,7 @@
                 mPipBoundsState.setBounds(toBounds);
             } else {
                 mPipBoundsState.getMotionBoundsState().setBoundsInMotion(toBounds);
-                /*
-                mPipTaskOrganizer.scheduleUserResizePip(getBounds(), toBounds,
-                        (Rect newBounds) -> {
-                                mMenuController.updateMenuLayout(newBounds);
-                        });
-                 */
+                mPipScheduler.scheduleUserResizePip(toBounds);
             }
         } else {
             // If PIP is 'catching up' after being stuck in the dismiss target, update the animation
@@ -552,11 +562,11 @@
     /** Set new fling configs whose min/max values respect the given movement bounds. */
     private void rebuildFlingConfigs() {
         mFlingConfigX = new PhysicsAnimator.FlingConfig(DEFAULT_FRICTION,
-                mPipBoundsState.getMovementBounds().left,
-                mPipBoundsState.getMovementBounds().right);
+                mPipBoundsAlgorithm.getMovementBounds(getBounds()).left,
+                mPipBoundsAlgorithm.getMovementBounds(getBounds()).right);
         mFlingConfigY = new PhysicsAnimator.FlingConfig(DEFAULT_FRICTION,
-                mPipBoundsState.getMovementBounds().top,
-                mPipBoundsState.getMovementBounds().bottom);
+                mPipBoundsAlgorithm.getMovementBounds(getBounds()).top,
+                mPipBoundsAlgorithm.getMovementBounds(getBounds()).bottom);
         final Rect insetBounds = mPipBoundsState.getDisplayLayout().stableInsets();
         mStashConfigX = new PhysicsAnimator.FlingConfig(
                 DEFAULT_FRICTION,
@@ -623,22 +633,15 @@
     private void onBoundsPhysicsAnimationEnd() {
         // The physics animation ended, though we may not necessarily be done animating, such as
         // when we're still dragging after moving out of the magnetic target.
-        if (!mDismissalPending
-                && !mSpringingToTouch
-                && !mMagnetizedPip.getObjectStuckToTarget()) {
-            // All motion operations have actually finished.
-            mPipBoundsState.setBounds(
-                    mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
-            mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded();
-            if (!mDismissalPending) {
-                // do not schedule resize if PiP is dismissing, which may cause app re-open to
-                // mBounds instead of its normal bounds.
-                // mPipTaskOrganizer.scheduleFinishResizePip(getBounds());
-            }
+        if (!mDismissalPending && !mSpringingToTouch && !mMagnetizedPip.getObjectStuckToTarget()) {
+            // do not schedule resize if PiP is dismissing, which may cause app re-open to
+            // mBounds instead of its normal bounds.
+            Bundle extra = new Bundle();
+            extra.putBoolean(FLING_BOUNDS_CHANGE, true);
+            mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
+            return;
         }
-        mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
-        mSpringingToTouch = false;
-        mDismissalPending = false;
+        settlePipBoundsAfterPhysicsAnimation(true /* animatingAfter */);
         cleanUpHighPerfSessionMaybe();
     }
 
@@ -662,7 +665,7 @@
                             + " callers=\n%s", TAG, toBounds, Debug.getCallers(5, "    "));
         }
         if (!toBounds.equals(getBounds())) {
-            // mPipTaskOrganizer.scheduleResizePip(toBounds, mUpdateBoundsCallback);
+            mPipScheduler.scheduleAnimateResizePip(toBounds);
         }
     }
 
@@ -685,6 +688,61 @@
         // setAnimatingToBounds(toBounds);
     }
 
+    @Override
+    public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+            @PipTransitionState.TransitionState int newState,
+            @Nullable Bundle extra) {
+        switch (newState) {
+            case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
+                if (!extra.getBoolean(FLING_BOUNDS_CHANGE)) break;
+
+                // If touch is turned off and we are in a fling animation, schedule a transition.
+                mWaitingForBoundsChangeTransition = true;
+                mPipScheduler.scheduleAnimateResizePip(
+                        mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
+                break;
+            case PipTransitionState.CHANGING_PIP_BOUNDS:
+                if (!mWaitingForBoundsChangeTransition) break;
+
+                // If bounds change transition was scheduled from this class, handle leash updates.
+                mWaitingForBoundsChangeTransition = false;
+                SurfaceControl.Transaction startTx = extra.getParcelable(
+                        PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
+                Rect destinationBounds = extra.getParcelable(
+                        PipTransition.PIP_DESTINATION_BOUNDS, Rect.class);
+                startTx.setPosition(mPipTransitionState.mPinnedTaskLeash,
+                        destinationBounds.left, destinationBounds.top);
+                startTx.apply();
+
+                // All motion operations have actually finished, so make bounds cache updates.
+                settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
+                cleanUpHighPerfSessionMaybe();
+
+                // Setting state to CHANGED_PIP_BOUNDS applies finishTx and notifies Core.
+                mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS);
+                break;
+            case PipTransitionState.EXITING_PIP:
+                // We need to force finish any local animators if about to leave PiP, to avoid
+                // breaking the state (e.g. leashes are cleaned up upon exit).
+                if (!mPipBoundsState.getMotionBoundsState().isInMotion()) break;
+                cancelPhysicsAnimation();
+                settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
+        }
+    }
+
+    private void settlePipBoundsAfterPhysicsAnimation(boolean animatingAfter) {
+        if (!animatingAfter) {
+            // The physics animation ended, though we may not necessarily be done animating, such as
+            // when we're still dragging after moving out of the magnetic target. Only set the final
+            // bounds state and clear motion bounds completely if the whole animation is over.
+            mPipBoundsState.setBounds(mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
+            mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded();
+        }
+        mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
+        mSpringingToTouch = false;
+        mDismissalPending = false;
+    }
+
     /**
      * Returns a MagnetizedObject wrapper for PIP's animated bounds. This is provided to the
      * magnetic dismiss target so it can calculate PIP's size and position.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
index 04cf350..b55a41d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
@@ -24,6 +24,7 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.hardware.input.InputManager;
+import android.os.Bundle;
 import android.os.Looper;
 import android.view.BatchedInputEventReceiver;
 import android.view.Choreographer;
@@ -32,6 +33,7 @@
 import android.view.InputEventReceiver;
 import android.view.InputMonitor;
 import android.view.MotionEvent;
+import android.view.SurfaceControl;
 import android.view.ViewConfiguration;
 
 import androidx.annotation.VisibleForTesting;
@@ -51,16 +53,20 @@
  * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
  * trigger dynamic resize.
  */
-public class PipResizeGestureHandler {
+public class PipResizeGestureHandler implements
+        PipTransitionState.PipTransitionStateChangedListener {
 
     private static final String TAG = "PipResizeGestureHandler";
     private static final int PINCH_RESIZE_SNAP_DURATION = 250;
     private static final float PINCH_RESIZE_AUTO_MAX_RATIO = 0.9f;
+    private static final String RESIZE_BOUNDS_CHANGE = "resize_bounds_change";
 
     private final Context mContext;
     private final PipBoundsAlgorithm mPipBoundsAlgorithm;
     private final PipBoundsState mPipBoundsState;
     private final PipTouchState mPipTouchState;
+    private final PipScheduler mPipScheduler;
+    private final PipTransitionState mPipTransitionState;
     private final PhonePipMenuController mPhonePipMenuController;
     private final PipUiEventLogger mPipUiEventLogger;
     private final PipPinchResizingAlgorithm mPinchResizingAlgorithm;
@@ -88,6 +94,7 @@
     private boolean mIsSysUiStateValid;
     private boolean mThresholdCrossed;
     private boolean mOngoingPinchToResize = false;
+    private boolean mWaitingForBoundsChangeTransition = false;
     private float mAngle = 0;
     int mFirstIndex = -1;
     int mSecondIndex = -1;
@@ -104,11 +111,17 @@
     private int mCtrlType;
     private int mOhmOffset;
 
-    public PipResizeGestureHandler(Context context, PipBoundsAlgorithm pipBoundsAlgorithm,
-            PipBoundsState pipBoundsState, PipTouchState pipTouchState,
+    public PipResizeGestureHandler(Context context,
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipBoundsState pipBoundsState,
+            PipTouchState pipTouchState,
+            PipScheduler pipScheduler,
+            PipTransitionState pipTransitionState,
             Runnable updateMovementBoundsRunnable,
-            PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController,
-            ShellExecutor mainExecutor, @Nullable PipPerfHintController pipPerfHintController) {
+            PipUiEventLogger pipUiEventLogger,
+            PhonePipMenuController menuActivityController,
+            ShellExecutor mainExecutor,
+            @Nullable PipPerfHintController pipPerfHintController) {
         mContext = context;
         mDisplayId = context.getDisplayId();
         mMainExecutor = mainExecutor;
@@ -116,6 +129,11 @@
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
         mPipBoundsState = pipBoundsState;
         mPipTouchState = pipTouchState;
+        mPipScheduler = pipScheduler;
+
+        mPipTransitionState = pipTransitionState;
+        mPipTransitionState.addPipTransitionStateChangedListener(this);
+
         mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
         mPhonePipMenuController = menuActivityController;
         mPipUiEventLogger = pipUiEventLogger;
@@ -125,6 +143,7 @@
             mUserResizeBounds.set(rect);
             // mMotionHelper.synchronizePinnedStackBounds();
             mUpdateMovementBoundsRunnable.run();
+            mPipBoundsState.setBounds(rect);
             resetState();
         };
     }
@@ -202,7 +221,7 @@
     @VisibleForTesting
     void onInputEvent(InputEvent ev) {
         if (!mEnablePinchResize) {
-            // No need to handle anything if neither form of resizing is enabled.
+            // No need to handle anything if resizing isn't enabled.
             return;
         }
 
@@ -227,7 +246,7 @@
                 }
             }
 
-            if (mEnablePinchResize && mOngoingPinchToResize) {
+            if (mOngoingPinchToResize) {
                 onPinchResize(mv);
             }
         }
@@ -249,13 +268,11 @@
     }
 
     boolean willStartResizeGesture(MotionEvent ev) {
-        if (isInValidSysUiState()) {
-            if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
-                if (mEnablePinchResize && ev.getPointerCount() == 2) {
-                    onPinchResize(ev);
-                    mOngoingPinchToResize = mAllowGesture;
-                    return mAllowGesture;
-                }
+        if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
+            if (mEnablePinchResize && ev.getPointerCount() == 2) {
+                onPinchResize(ev);
+                mOngoingPinchToResize = mAllowGesture;
+                return mAllowGesture;
             }
         }
         return false;
@@ -284,7 +301,6 @@
             mSecondIndex = -1;
             mAllowGesture = false;
             finishResize();
-            cleanUpHighPerfSessionMaybe();
         }
 
         if (ev.getPointerCount() != 2) {
@@ -347,10 +363,7 @@
                         mDownSecondPoint, mLastPoint, mLastSecondPoint, mMinSize, mMaxSize,
                         mDownBounds, mLastResizeBounds);
 
-                /*
-                mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds,
-                        mAngle, null);
-                 */
+                mPipScheduler.scheduleUserResizePip(mLastResizeBounds, mAngle);
                 mPipBoundsState.setHasUserResizedPip(true);
             }
         }
@@ -399,57 +412,43 @@
     }
 
     private void finishResize() {
-        if (!mLastResizeBounds.isEmpty()) {
-            // Pinch-to-resize needs to re-calculate snap fraction and animate to the snapped
-            // position correctly. Drag-resize does not need to move, so just finalize resize.
-            if (mOngoingPinchToResize) {
-                final Rect startBounds = new Rect(mLastResizeBounds);
-                // If user resize is pretty close to max size, just auto resize to max.
-                if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x
-                        || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
-                    resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y);
-                }
-
-                // If user resize is smaller than min size, auto resize to min
-                if (mLastResizeBounds.width() < mMinSize.x
-                        || mLastResizeBounds.height() < mMinSize.y) {
-                    resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y);
-                }
-
-                // get the current movement bounds
-                final Rect movementBounds = mPipBoundsAlgorithm
-                        .getMovementBounds(mLastResizeBounds);
-
-                // snap mLastResizeBounds to the correct edge based on movement bounds
-                snapToMovementBoundsEdge(mLastResizeBounds, movementBounds);
-
-                final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
-                        mLastResizeBounds, movementBounds);
-                mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
-
-                // disable any touch events beyond resizing too
-                mPipTouchState.setAllowInputEvents(false);
-
-                /*
-                mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
-                        PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback, () -> {
-                            // enable touch events
-                            mPipTouchState.setAllowInputEvents(true);
-                        });
-                 */
-            } else {
-                /*
-                mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
-                        TRANSITION_DIRECTION_USER_RESIZE,
-                        mUpdateResizeBoundsCallback);
-                 */
-            }
-            final float magnetRadiusPercent = (float) mLastResizeBounds.width() / mMinSize.x / 2.f;
-            mPipUiEventLogger.log(
-                    PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
-        } else {
+        if (mLastResizeBounds.isEmpty()) {
             resetState();
         }
+        if (!mOngoingPinchToResize) {
+            return;
+        }
+        final Rect startBounds = new Rect(mLastResizeBounds);
+
+        // If user resize is pretty close to max size, just auto resize to max.
+        if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x
+                || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
+            resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y);
+        }
+
+        // If user resize is smaller than min size, auto resize to min
+        if (mLastResizeBounds.width() < mMinSize.x
+                || mLastResizeBounds.height() < mMinSize.y) {
+            resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y);
+        }
+
+        // get the current movement bounds
+        final Rect movementBounds = mPipBoundsAlgorithm
+                .getMovementBounds(mLastResizeBounds);
+
+        // snap mLastResizeBounds to the correct edge based on movement bounds
+        snapToMovementBoundsEdge(mLastResizeBounds, movementBounds);
+
+        final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
+                mLastResizeBounds, movementBounds);
+        mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
+
+        // Update the transition state to schedule a resize transition.
+        Bundle extra = new Bundle();
+        extra.putBoolean(RESIZE_BOUNDS_CHANGE, true);
+        mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
+
+        mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
     }
 
     private void resetState() {
@@ -509,6 +508,40 @@
         rect.set(l, t, r, b);
     }
 
+    @Override
+    public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+            @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
+        switch (newState) {
+            case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
+                if (!extra.getBoolean(RESIZE_BOUNDS_CHANGE)) break;
+                mWaitingForBoundsChangeTransition = true;
+                mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds);
+                break;
+            case PipTransitionState.CHANGING_PIP_BOUNDS:
+                if (!mWaitingForBoundsChangeTransition) break;
+
+                // If bounds change transition was scheduled from this class, handle leash updates.
+                mWaitingForBoundsChangeTransition = false;
+
+                SurfaceControl.Transaction startTx = extra.getParcelable(
+                        PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
+                Rect destinationBounds = extra.getParcelable(
+                        PipTransition.PIP_DESTINATION_BOUNDS, Rect.class);
+                startTx.setPosition(mPipTransitionState.mPinnedTaskLeash,
+                        destinationBounds.left, destinationBounds.top);
+                startTx.apply();
+
+                // All motion operations have actually finished, so make bounds cache updates.
+                cleanUpHighPerfSessionMaybe();
+
+                // Setting state to CHANGED_PIP_BOUNDS applies finishTx and notifies Core.
+                mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS);
+
+                mUpdateResizeBoundsCallback.accept(destinationBounds);
+                break;
+        }
+    }
+
     /**
      * Dumps the {@link PipResizeGestureHandler} state.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 72fa3ba..4947507 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -24,17 +24,21 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.graphics.Matrix;
 import android.graphics.Rect;
+import android.view.SurfaceControl;
 import android.window.WindowContainerTransaction;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 import androidx.core.content.ContextCompat;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -156,4 +160,39 @@
         wct.setBounds(mPipTransitionState.mPipTaskToken, toBounds);
         mPipTransitionController.startResizeTransition(wct);
     }
+
+    /**
+     * Directly perform a scaled matrix transformation on the leash. This will not perform any
+     * {@link WindowContainerTransaction}.
+     */
+    public void scheduleUserResizePip(Rect toBounds) {
+        scheduleUserResizePip(toBounds, 0f /* degrees */);
+    }
+
+    /**
+     * Directly perform a scaled matrix transformation on the leash. This will not perform any
+     * {@link WindowContainerTransaction}.
+     *
+     * @param degrees the angle to rotate the bounds to.
+     */
+    public void scheduleUserResizePip(Rect toBounds, float degrees) {
+        if (toBounds.isEmpty()) {
+            ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: Attempted to user resize PIP to empty bounds, aborting.", TAG);
+            return;
+        }
+        SurfaceControl leash = mPipTransitionState.mPinnedTaskLeash;
+        final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+
+        Matrix transformTensor = new Matrix();
+        final float[] mMatrixTmp = new float[9];
+        final float scale = (float) toBounds.width() / mPipBoundsState.getBounds().width();
+
+        transformTensor.setScale(scale, scale);
+        transformTensor.postTranslate(toBounds.left, toBounds.top);
+        transformTensor.postRotate(degrees, toBounds.centerX(), toBounds.centerY());
+
+        tx.setMatrix(leash, transformTensor, mMatrixTmp);
+        tx.apply();
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index 472003c..319d199 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -16,6 +16,8 @@
 
 package com.android.wm.shell.pip2.phone;
 
+import static android.view.WindowManager.INPUT_CONSUMER_PIP;
+
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING;
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD;
 import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT;
@@ -30,18 +32,19 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.provider.DeviceConfig;
 import android.util.Size;
 import android.view.DisplayCutout;
 import android.view.InputEvent;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
+import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -70,7 +73,7 @@
  * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
  * the PIP.
  */
-public class PipTouchHandler {
+public class PipTouchHandler implements PipTransitionState.PipTransitionStateChangedListener {
 
     private static final String TAG = "PipTouchHandler";
     private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
@@ -80,6 +83,8 @@
     private final Context mContext;
     private final PipBoundsAlgorithm mPipBoundsAlgorithm;
     @NonNull private final PipBoundsState mPipBoundsState;
+    @NonNull private final PipTransitionState mPipTransitionState;
+    @NonNull private final PipScheduler mPipScheduler;
     @NonNull private final SizeSpecSource mSizeSpecSource;
     private final PipUiEventLogger mPipUiEventLogger;
     private final PipDismissTargetHandler mPipDismissTargetHandler;
@@ -125,6 +130,7 @@
     private final FloatingContentCoordinator mFloatingContentCoordinator;
     private PipMotionHelper mMotionHelper;
     private PipTouchGesture mGesture;
+    private PipInputConsumer mPipInputConsumer;
 
     // Temp vars
     private final Rect mTmpBounds = new Rect();
@@ -167,6 +173,8 @@
             PhonePipMenuController menuController,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             @NonNull PipBoundsState pipBoundsState,
+            @NonNull PipTransitionState pipTransitionState,
+            @NonNull PipScheduler pipScheduler,
             @NonNull SizeSpecSource sizeSpecSource,
             PipMotionHelper pipMotionHelper,
             FloatingContentCoordinator floatingContentCoordinator,
@@ -179,6 +187,10 @@
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
         mPipBoundsState = pipBoundsState;
+
+        mPipTransitionState = pipTransitionState;
+        mPipTransitionState.addPipTransitionStateChangedListener(this::onPipTransitionStateChanged);
+        mPipScheduler = pipScheduler;
         mSizeSpecSource = sizeSpecSource;
         mMenuController = menuController;
         mPipUiEventLogger = pipUiEventLogger;
@@ -204,10 +216,10 @@
                 },
                 menuController::hideMenu,
                 mainExecutor);
-        mPipResizeGestureHandler =
-                new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
-                        mTouchState, this::updateMovementBounds, pipUiEventLogger,
-                        menuController, mainExecutor, mPipPerfHintController);
+        mPipResizeGestureHandler = new PipResizeGestureHandler(context, pipBoundsAlgorithm,
+                pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState,
+                this::updateMovementBounds, pipUiEventLogger, menuController, mainExecutor,
+                mPipPerfHintController);
         mPipBoundsState.addOnAspectRatioChangedCallback(this::updateMinMaxSize);
 
         if (PipUtils.isPip2ExperimentEnabled()) {
@@ -227,6 +239,11 @@
         mPipResizeGestureHandler.init();
         mPipDismissTargetHandler.init();
 
+        mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
+                INPUT_CONSUMER_PIP, mMainExecutor);
+        mPipInputConsumer.setInputListener(this::handleTouchEvent);
+        mPipInputConsumer.setRegistrationListener(this::onRegistrationChanged);
+
         mEnableStash = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 PIP_STASHING,
@@ -294,19 +311,17 @@
 
     void onActivityPinned() {
         mPipDismissTargetHandler.createOrUpdateDismissTarget();
-
         mPipResizeGestureHandler.onActivityPinned();
         mFloatingContentCoordinator.onContentAdded(mMotionHelper);
+        mPipInputConsumer.registerInputConsumer();
     }
 
-    void onActivityUnpinned(ComponentName topPipActivity) {
-        if (topPipActivity == null) {
-            // Clean up state after the last PiP activity is removed
-            mPipDismissTargetHandler.cleanUpDismissTarget();
-
-            mFloatingContentCoordinator.onContentRemoved(mMotionHelper);
-        }
+    void onActivityUnpinned() {
+        // Clean up state after the last PiP activity is removed
+        mPipDismissTargetHandler.cleanUpDismissTarget();
+        mFloatingContentCoordinator.onContentRemoved(mMotionHelper);
         mPipResizeGestureHandler.onActivityUnpinned();
+        mPipInputConsumer.unregisterInputConsumer();
     }
 
     void onPinnedStackAnimationEnded(
@@ -512,6 +527,7 @@
             return true;
         }
 
+        /*
         if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting())
                 && mPipDismissTargetHandler.maybeConsumeMotionEvent(ev)) {
             // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event
@@ -528,11 +544,13 @@
             return true;
         }
 
-        if (!mTouchState.isUserInteracting()) {
+        // Ignore the motion event When the entry animation is waiting to be started
+        if (!mTouchState.isUserInteracting() && mPipTaskOrganizer.isEntryScheduled()) {
             ProtoLog.wtf(WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: Waiting to start the entry animation, skip the motion event.", TAG);
             return true;
         }
+         */
 
         // Update the touch state
         mTouchState.onTouchEvent(ev);
@@ -808,7 +826,7 @@
             mMovementWithinDismiss = touchState.getDownTouchPosition().y
                     >= mPipBoundsState.getMovementBounds().bottom;
             mMotionHelper.setSpringingToTouch(false);
-            // mPipDismissTargetHandler.setTaskLeash(mPipTaskOrganizer.getSurfaceControl());
+            mPipDismissTargetHandler.setTaskLeash(mPipTransitionState.mPinnedTaskLeash);
 
             // If the menu is still visible then just poke the menu
             // so that it will timeout after the user stops touching it
@@ -880,7 +898,8 @@
                 // Reset the touch state on up before the fling settles
                 mTouchState.reset();
                 if (mEnableStash && shouldStash(vel, getPossiblyMotionBounds())) {
-                    mMotionHelper.stashToEdge(vel.x, vel.y, this::stashEndAction /* endAction */);
+                    // mMotionHelper.stashToEdge(vel.x, vel.y,
+                    //      this::stashEndAction /* endAction */);
                 } else {
                     if (mPipBoundsState.isStashed()) {
                         // Reset stashed state if previously stashed
@@ -1059,6 +1078,28 @@
         mPipResizeGestureHandler.setOhmOffset(offset);
     }
 
+    @Override
+    public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+            @PipTransitionState.TransitionState int newState,
+            @Nullable Bundle extra) {
+        switch (newState) {
+            case PipTransitionState.ENTERED_PIP:
+                onActivityPinned();
+                mTouchState.setAllowInputEvents(true);
+                break;
+            case PipTransitionState.EXITED_PIP:
+                mTouchState.setAllowInputEvents(false);
+                onActivityUnpinned();
+                break;
+            case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
+                mTouchState.setAllowInputEvents(false);
+                break;
+            case PipTransitionState.CHANGED_PIP_BOUNDS:
+                mTouchState.setAllowInputEvents(true);
+                break;
+        }
+    }
+
     /**
      * Dumps the {@link PipTouchHandler} state.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 12dce5b..7dddd27 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -45,7 +45,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.util.Preconditions;
-import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.common.pip.PipBoundsState;
@@ -62,9 +61,16 @@
 public class PipTransition extends PipTransitionController implements
         PipTransitionState.PipTransitionStateChangedListener {
     private static final String TAG = PipTransition.class.getSimpleName();
+
+    // Used when for ENTERING_PIP state update.
     private static final String PIP_TASK_TOKEN = "pip_task_token";
     private static final String PIP_TASK_LEASH = "pip_task_leash";
 
+    // Used for PiP CHANGING_BOUNDS state update.
+    static final String PIP_START_TX = "pip_start_tx";
+    static final String PIP_FINISH_TX = "pip_finish_tx";
+    static final String PIP_DESTINATION_BOUNDS = "pip_dest_bounds";
+
     /**
      * The fixed start delay in ms when fading out the content overlay from bounds animation.
      * The fadeout animation is guaranteed to start after the client has drawn under the new config.
@@ -98,6 +104,8 @@
     private WindowContainerToken mPipTaskToken;
     @Nullable
     private SurfaceControl mPipLeash;
+    @Nullable
+    private Transitions.TransitionFinishCallback mFinishCallback;
 
     public PipTransition(
             Context context,
@@ -223,7 +231,6 @@
             return startExpandAnimation(info, startTransaction, finishTransaction, finishCallback);
         } else if (transition == mResizeTransition) {
             mResizeTransition = null;
-            mPipTransitionState.setState(PipTransitionState.CHANGING_PIP_BOUNDS);
             return startResizeAnimation(info, startTransaction, finishTransaction, finishCallback);
         }
 
@@ -246,31 +253,27 @@
             return false;
         }
         SurfaceControl pipLeash = pipChange.getLeash();
-        Rect destinationBounds = pipChange.getEndAbsBounds();
 
         // Even though the final bounds and crop are applied with finishTransaction since
         // this is a visible change, we still need to handle the app draw coming in. Snapshot
         // covering app draw during collection will be removed by startTransaction. So we make
-        // the crop equal to the final bounds and then scale the leash back to starting bounds.
+        // the crop equal to the final bounds and then let the current
+        // animator scale the leash back to starting bounds.
+        // Note: animator is responsible for applying the startTx but NOT finishTx.
         startTransaction.setWindowCrop(pipLeash, pipChange.getEndAbsBounds().width(),
                 pipChange.getEndAbsBounds().height());
-        startTransaction.setScale(pipLeash,
-                (float) mPipBoundsState.getBounds().width() / destinationBounds.width(),
-                (float) mPipBoundsState.getBounds().height() / destinationBounds.height());
-        startTransaction.apply();
 
-        finishTransaction.setScale(pipLeash,
-                (float) mPipBoundsState.getBounds().width() / destinationBounds.width(),
-                (float) mPipBoundsState.getBounds().height() / destinationBounds.height());
-
-        // We are done with the transition, but will continue animating leash to final bounds.
-        finishCallback.onTransitionFinished(null);
-
-        // Animate the pip leash with the new buffer
-        final int duration = mContext.getResources().getInteger(
-                R.integer.config_pipResizeAnimationDuration);
         // TODO: b/275910498 Couple this routine with a new implementation of the PiP animator.
-        startResizeAnimation(pipLeash, mPipBoundsState.getBounds(), destinationBounds, duration);
+        // Classes interested in continuing the animation would subscribe to this state update
+        // getting info such as endBounds, startTx, and finishTx as an extra Bundle once
+        // animators are in place. Once done state needs to be updated to CHANGED_PIP_BOUNDS.
+        Bundle extra = new Bundle();
+        extra.putParcelable(PIP_START_TX, startTransaction);
+        extra.putParcelable(PIP_FINISH_TX, finishTransaction);
+        extra.putParcelable(PIP_DESTINATION_BOUNDS, pipChange.getEndAbsBounds());
+
+        mFinishCallback = finishCallback;
+        mPipTransitionState.setState(PipTransitionState.CHANGING_PIP_BOUNDS, extra);
         return true;
     }
 
@@ -285,12 +288,17 @@
         WindowContainerToken pipTaskToken = pipChange.getContainer();
         SurfaceControl pipLeash = pipChange.getLeash();
 
+        if (pipTaskToken == null || pipLeash == null) {
+            return false;
+        }
+
         PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams;
         Rect srcRectHint = params.getSourceRectHint();
         Rect startBounds = pipChange.getStartAbsBounds();
         Rect destinationBounds = pipChange.getEndAbsBounds();
 
         WindowContainerTransaction finishWct = new WindowContainerTransaction();
+        SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
 
         if (PipBoundsAlgorithm.isSourceRectHintValidForEnterPip(srcRectHint, destinationBounds)) {
             final float scale = (float) destinationBounds.width() / srcRectHint.width();
@@ -316,19 +324,17 @@
                     .reparent(overlayLeash, pipLeash)
                     .setLayer(overlayLeash, Integer.MAX_VALUE);
 
-            if (pipTaskToken != null) {
-                SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
-                tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(),
-                                this::onClientDrawAtTransitionEnd)
-                        .setScale(overlayLeash, 1f, 1f)
-                        .setPosition(overlayLeash,
-                                (destinationBounds.width() - overlaySize) / 2f,
-                                (destinationBounds.height() - overlaySize) / 2f);
-                finishWct.setBoundsChangeTransaction(pipTaskToken, tx);
-            }
+            // Overlay needs to be adjusted once a new draw comes in resetting surface transform.
+            tx.setScale(overlayLeash, 1f, 1f);
+            tx.setPosition(overlayLeash, (destinationBounds.width() - overlaySize) / 2f,
+                    (destinationBounds.height() - overlaySize) / 2f);
         }
         startTransaction.apply();
 
+        tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(),
+                        this::onClientDrawAtTransitionEnd);
+        finishWct.setBoundsChangeTransaction(pipTaskToken, tx);
+
         // Note that finishWct should be free of any actual WM state changes; we are using
         // it for syncing with the client draw after delayed configuration changes are dispatched.
         finishCallback.onTransitionFinished(finishWct.isEmpty() ? null : finishWct);
@@ -412,14 +418,6 @@
         return true;
     }
 
-    /**
-     * TODO: b/275910498 Use a new implementation of the PiP animator here.
-     */
-    private void startResizeAnimation(SurfaceControl leash, Rect startBounds,
-            Rect endBounds, int duration) {
-        mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS);
-    }
-
     //
     // Various helpers to resolve transition requests and infos
     //
@@ -537,6 +535,15 @@
                 mPipTransitionState.mPipTaskToken = null;
                 mPipTransitionState.mPinnedTaskLeash = null;
                 break;
+            case PipTransitionState.CHANGED_PIP_BOUNDS:
+                // Note: this might not be the end of the animation, rather animator just finished
+                // adjusting startTx and finishTx and is ready to finishTransition(). The animator
+                // can still continue playing the leash into the destination bounds after.
+                if (mFinishCallback != null) {
+                    mFinishCallback.onTransitionFinished(null);
+                    mFinishCallback = null;
+                }
+                break;
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index f7bc622..8204d41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -71,17 +71,21 @@
     // State for app finishing drawing in PiP mode as a final step in enter PiP flow.
     public static final int ENTERED_PIP = 3;
 
-    // State for scheduling a transition to change PiP bounds.
-    public static final int CHANGING_PIP_BOUNDS = 4;
+    // State to indicate we have scheduled a PiP bounds change transition.
+    public static final int SCHEDULED_BOUNDS_CHANGE = 4;
 
-    // State for app potentially finishing drawing in new PiP bounds after resize is complete.
-    public static final int CHANGED_PIP_BOUNDS = 5;
+    // State for the start of playing a transition to change PiP bounds. At this point, WM Core
+    // is aware of the new PiP bounds, but Shell might still be continuing animating.
+    public static final int CHANGING_PIP_BOUNDS = 5;
+
+    // State for finishing animating into new PiP bounds after resize is complete.
+    public static final int CHANGED_PIP_BOUNDS = 6;
 
     // State for starting exiting PiP.
-    public static final int EXITING_PIP = 6;
+    public static final int EXITING_PIP = 7;
 
     // State for finishing exit PiP flow.
-    public static final int EXITED_PIP = 7;
+    public static final int EXITED_PIP = 8;
 
     private static final int FIRST_CUSTOM_STATE = 1000;
 
@@ -92,6 +96,7 @@
             SWIPING_TO_PIP,
             ENTERING_PIP,
             ENTERED_PIP,
+            SCHEDULED_BOUNDS_CHANGE,
             CHANGING_PIP_BOUNDS,
             CHANGED_PIP_BOUNDS,
             EXITING_PIP,
@@ -165,10 +170,11 @@
      * @param extra a bundle passed to the subscribed listeners to resolve/cache extra info.
      */
     public void setState(@TransitionState int state, @Nullable Bundle extra) {
-        if (state == ENTERING_PIP || state == SWIPING_TO_PIP) {
-            // Whenever we are entering PiP caller must provide extra state to set as well.
+        if (state == ENTERING_PIP || state == SWIPING_TO_PIP
+                || state == SCHEDULED_BOUNDS_CHANGE || state == CHANGING_PIP_BOUNDS) {
+            // States listed above require extra bundles to be provided.
             Preconditions.checkArgument(extra != null && !extra.isEmpty(),
-                    "No extra bundle for either ENTERING_PIP or SWIPING_TO_PIP state.");
+                    "No extra bundle for " + stateToString(state) + " state.");
         }
         if (mState != state) {
             dispatchPipTransitionStateChanged(mState, state, extra);
@@ -254,22 +260,24 @@
         return ++mPrevCustomState;
     }
 
-    private String stateToString() {
-        switch (mState) {
+    private static String stateToString(int state) {
+        switch (state) {
             case UNDEFINED: return "undefined";
+            case SWIPING_TO_PIP: return "swiping_to_pip";
             case ENTERING_PIP: return "entering-pip";
             case ENTERED_PIP: return "entered-pip";
+            case SCHEDULED_BOUNDS_CHANGE: return "scheduled_bounds_change";
             case CHANGING_PIP_BOUNDS: return "changing-bounds";
             case CHANGED_PIP_BOUNDS: return "changed-bounds";
             case EXITING_PIP: return "exiting-pip";
             case EXITED_PIP: return "exited-pip";
         }
-        throw new IllegalStateException("Unknown state: " + mState);
+        throw new IllegalStateException("Unknown state: " + state);
     }
 
     @Override
     public String toString() {
         return String.format("PipTransitionState(mState=%s, mInSwipePipToHomeTransition=%b)",
-                stateToString(), mInSwipePipToHomeTransition);
+                stateToString(mState), mInSwipePipToHomeTransition);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index ad29d15..19af3d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -52,7 +52,7 @@
     WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
     WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
-            Consts.TAG_WM_SHELL),
+            Consts.TAG_WM_DESKTOP_MODE),
     WM_SHELL_FLOATING_APPS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
     WM_SHELL_FOLDABLE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
@@ -120,6 +120,7 @@
         private static final String TAG_WM_SHELL = "WindowManagerShell";
         private static final String TAG_WM_STARTING_WINDOW = "ShellStartingWindow";
         private static final String TAG_WM_SPLIT_SCREEN = "ShellSplitScreen";
+        private static final String TAG_WM_DESKTOP_MODE = "ShellDesktopMode";
 
         private static final boolean ENABLE_DEBUG = true;
         private static final boolean ENABLE_LOG_TO_PROTO_DEBUG = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index c5f23a8..c53e7fe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -50,9 +50,9 @@
 import com.android.wm.shell.common.SingleInstanceRemoteListener;
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.DesktopModeStatus;
 import com.android.wm.shell.shared.annotations.ExternalThread;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -327,7 +327,8 @@
 
     private boolean shouldEnableRunningTasksForDesktopMode() {
         return mPcFeatureEnabled
-                || (DesktopModeStatus.isEnabled() && enableDesktopWindowingTaskbarRunningApps());
+                || (DesktopModeStatus.canEnterDesktopMode(mContext)
+                && enableDesktopWindowingTaskbarRunningApps());
     }
 
     @VisibleForTesting
@@ -371,7 +372,8 @@
                 continue;
             }
 
-            if (DesktopModeStatus.isEnabled() && mDesktopModeTaskRepository.isPresent()
+            if (DesktopModeStatus.canEnterDesktopMode(mContext)
+                    && mDesktopModeTaskRepository.isPresent()
                     && mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) {
                 if (mDesktopModeTaskRepository.get().isMinimizedTask(taskInfo.taskId)) {
                     // Minimized freeform tasks should not be shown at all.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 6aad4e2..8df287d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -69,7 +69,9 @@
         default void onSplitVisibilityChanged(boolean visible) {}
     }
 
-    /** Callback interface for listening to requests to enter split select */
+    /**
+     * Callback interface for listening to requests to enter split select. Used for desktop -> split
+     */
     interface SplitSelectListener {
         default boolean onRequestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
                 int splitPosition, Rect taskBounds) {
@@ -90,6 +92,24 @@
     /** Unregisters listener that gets split screen callback. */
     void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
 
+    interface SplitInvocationListener {
+        /**
+         * Called whenever shell starts or stops the split screen animation
+         * @param animationRunning if {@code true} the animation has begun, if {@code false} the
+         *                         animation has finished
+         */
+        default void onSplitAnimationInvoked(boolean animationRunning) { }
+    }
+
+    /**
+     * Registers a {@link SplitInvocationListener} to notify when the animation to enter split
+     * screen has started and stopped
+     *
+     * @param executor callbacks to the listener will be executed on this executor
+     */
+    void registerSplitAnimationListener(@NonNull SplitInvocationListener listener,
+            @NonNull Executor executor);
+
     /** Called when device waking up finished. */
     void onFinishedWakingUp();
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 547457b..b9d70e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -1166,6 +1166,12 @@
         }
 
         @Override
+        public void registerSplitAnimationListener(@NonNull SplitInvocationListener listener,
+                @NonNull Executor executor) {
+            mStageCoordinator.registerSplitAnimationListener(listener, executor);
+        }
+
+        @Override
         public void onFinishedWakingUp() {
             mMainExecutor.execute(SplitScreenController.this::onFinishedWakingUp);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 1a53a1d..6e5b767 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -55,6 +55,7 @@
 import com.android.wm.shell.transition.Transitions;
 
 import java.util.ArrayList;
+import java.util.concurrent.Executor;
 
 /** Manages transition animations for split-screen. */
 class SplitScreenTransitions {
@@ -79,6 +80,8 @@
 
     private Transitions.TransitionFinishCallback mFinishCallback = null;
     private SurfaceControl.Transaction mFinishTransaction;
+    private SplitScreen.SplitInvocationListener mSplitInvocationListener;
+    private Executor mSplitInvocationListenerExecutor;
 
     SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
             @NonNull Runnable onFinishCallback, StageCoordinator stageCoordinator) {
@@ -353,6 +356,10 @@
                     + " skip to start enter split transition since it already exist. ");
             return null;
         }
+        if (mSplitInvocationListenerExecutor != null && mSplitInvocationListener != null) {
+            mSplitInvocationListenerExecutor.execute(() -> mSplitInvocationListener
+                    .onSplitAnimationInvoked(true /*animationRunning*/));
+        }
         final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
         setEnterTransition(transition, remoteTransition, extraTransitType, resizeAnim);
         return transition;
@@ -457,6 +464,7 @@
 
             mPendingEnter.onConsumed(aborted);
             mPendingEnter = null;
+            mStageCoordinator.notifySplitAnimationFinished();
             ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for enter transition");
         } else if (isPendingDismiss(transition)) {
             mPendingDismiss.onConsumed(aborted);
@@ -529,6 +537,12 @@
         mTransitions.getAnimExecutor().execute(va::start);
     }
 
+    public void registerSplitAnimListener(@NonNull SplitScreen.SplitInvocationListener listener,
+            @NonNull Executor executor) {
+        mSplitInvocationListener = listener;
+        mSplitInvocationListenerExecutor = executor;
+    }
+
     /** Calls when the transition got consumed. */
     interface TransitionConsumedCallback {
         void onConsumed(boolean aborted);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 5e9451a..4299088 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -43,10 +43,12 @@
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString;
+import static com.android.wm.shell.common.split.SplitScreenUtils.getResizingBackgroundColor;
 import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
 import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
 import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode;
 import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
@@ -67,6 +69,7 @@
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN;
 import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
+import static com.android.wm.shell.transition.MixedTransitionHelper.getPipReplacingChange;
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
@@ -157,6 +160,7 @@
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 /**
  * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
@@ -237,6 +241,9 @@
     private DefaultMixedHandler mMixedHandler;
     private final Toast mSplitUnsupportedToast;
     private SplitRequest mSplitRequest;
+    /** Used to notify others of when shell is animating into split screen */
+    private SplitScreen.SplitInvocationListener mSplitInvocationListener;
+    private Executor mSplitInvocationListenerExecutor;
 
     /**
      * Since StageCoordinator only coordinates MainStage and SideStage, it shouldn't support
@@ -247,6 +254,14 @@
         return false;
     }
 
+    /** NOTE: Will overwrite any previously set {@link #mSplitInvocationListener} */
+    public void registerSplitAnimationListener(
+            @NonNull SplitScreen.SplitInvocationListener listener, @NonNull Executor executor) {
+        mSplitInvocationListener = listener;
+        mSplitInvocationListenerExecutor = executor;
+        mSplitTransitions.registerSplitAnimListener(listener, executor);
+    }
+
     class SplitRequest {
         @SplitPosition
         int mActivatePosition;
@@ -535,7 +550,7 @@
                             null /* childrenToTop */, EXIT_REASON_UNKNOWN));
                     Log.w(TAG, splitFailureMessage("startShortcut",
                             "side stage was not populated"));
-                    mSplitUnsupportedToast.show();
+                    handleUnsupportedSplitStart();
                 }
 
                 if (finishedCallback != null) {
@@ -666,7 +681,7 @@
                             null /* childrenToTop */, EXIT_REASON_UNKNOWN));
                     Log.w(TAG, splitFailureMessage("startIntentLegacy",
                             "side stage was not populated"));
-                    mSplitUnsupportedToast.show();
+                    handleUnsupportedSplitStart();
                 }
 
                 if (apps != null) {
@@ -1287,7 +1302,7 @@
                             ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
             Log.w(TAG, splitFailureMessage("onRemoteAnimationFinishedOrCancelled",
                     "main or side stage was not populated."));
-            mSplitUnsupportedToast.show();
+            handleUnsupportedSplitStart();
         } else {
             mSyncQueue.queue(evictWct);
             mSyncQueue.runInSync(t -> {
@@ -1308,7 +1323,7 @@
                     ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
             Log.w(TAG, splitFailureMessage("onRemoteAnimationFinished",
                     "main or side stage was not populated"));
-            mSplitUnsupportedToast.show();
+            handleUnsupportedSplitStart();
             return;
         }
 
@@ -2374,14 +2389,20 @@
     }
 
     @Override
-    public void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY) {
+    public void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY,
+            boolean shouldUseParallaxEffect) {
         final SurfaceControl.Transaction t = mTransactionPool.acquire();
         t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
-        updateSurfaceBounds(layout, t, true /* applyResizingOffset */);
+        updateSurfaceBounds(layout, t, shouldUseParallaxEffect);
         getMainStageBounds(mTempRect1);
         getSideStageBounds(mTempRect2);
-        mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately);
-        mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately);
+        // TODO (b/307490004): "commonColor" below is a temporary fix to ensure the colors on both
+        //  sides match. When b/307490004 is fixed, this code can be reverted.
+        float[] commonColor = getResizingBackgroundColor(mSideStage.mRootTaskInfo).getComponents();
+        mMainStage.onResizing(
+                mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately, commonColor);
+        mSideStage.onResizing(
+                mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately, commonColor);
         t.apply();
         mTransactionPool.release(t);
     }
@@ -2824,7 +2845,7 @@
             mSplitLayout.setFreezeDividerWindow(false);
             final StageChangeRecord record = new StageChangeRecord();
             final int transitType = info.getType();
-            boolean hasEnteringPip = false;
+            TransitionInfo.Change pipChange = null;
             for (int iC = 0; iC < info.getChanges().size(); ++iC) {
                 final TransitionInfo.Change change = info.getChanges().get(iC);
                 if (change.getMode() == TRANSIT_CHANGE
@@ -2835,7 +2856,7 @@
                 }
 
                 if (mMixedHandler.isEnteringPip(change, transitType)) {
-                    hasEnteringPip = true;
+                    pipChange = change;
                 }
 
                 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
@@ -2887,9 +2908,20 @@
                 }
             }
 
-            if (hasEnteringPip) {
+            if (pipChange != null) {
+                TransitionInfo.Change pipReplacingChange = getPipReplacingChange(info, pipChange,
+                        mMainStage.mRootTaskInfo.taskId, mSideStage.mRootTaskInfo.taskId,
+                        getSplitItemStage(pipChange.getLastParent()));
+                if (pipReplacingChange != null) {
+                    // Set an enter transition for when startAnimation gets called again
+                    mSplitTransitions.setEnterTransition(transition, /*remoteTransition*/ null,
+                            TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, /*resizeAnim*/ false);
+                }
+
                 mMixedHandler.animatePendingEnterPipFromSplit(transition, info,
-                        startTransaction, finishTransaction, finishCallback);
+                        startTransaction, finishTransaction, finishCallback,
+                        pipReplacingChange != null);
+                notifySplitAnimationFinished();
                 return true;
             }
 
@@ -2924,6 +2956,7 @@
                 //                    the transition, or synchronize task-org callbacks.
             }
             // Use normal animations.
+            notifySplitAnimationFinished();
             return false;
         } else if (mMixedHandler != null && TransitionUtil.hasDisplayChange(info)) {
             // A display-change has been un-expectedly inserted into the transition. Redirect
@@ -2937,6 +2970,7 @@
                     mSplitLayout.update(startTransaction, true /* resetImePosition */);
                     startTransaction.apply();
                 }
+                notifySplitAnimationFinished();
                 return true;
             }
         }
@@ -3110,7 +3144,7 @@
                     pendingEnter.mRemoteHandler.onTransitionConsumed(transition,
                             false /*aborted*/, finishT);
                 }
-                mSplitUnsupportedToast.show();
+                handleUnsupportedSplitStart();
                 return true;
             }
         }
@@ -3139,6 +3173,7 @@
         final TransitionInfo.Change finalMainChild = mainChild;
         final TransitionInfo.Change finalSideChild = sideChild;
         enterTransition.setFinishedCallback((callbackWct, callbackT) -> {
+            notifySplitAnimationFinished();
             if (finalMainChild != null) {
                 if (!mainNotContainOpenTask) {
                     mMainStage.evictOtherChildren(callbackWct, finalMainChild.getTaskInfo().taskId);
@@ -3560,6 +3595,19 @@
                 mSplitLayout.isLeftRightSplit());
     }
 
+    private void handleUnsupportedSplitStart() {
+        mSplitUnsupportedToast.show();
+        notifySplitAnimationFinished();
+    }
+
+    void notifySplitAnimationFinished() {
+        if (mSplitInvocationListener == null || mSplitInvocationListenerExecutor == null) {
+            return;
+        }
+        mSplitInvocationListenerExecutor.execute(() ->
+                mSplitInvocationListener.onSplitAnimationInvoked(false /*animationRunning*/));
+    }
+
     /**
      * Logs the exit of splitscreen to a specific stage. This must be called before the exit is
      * executed.
@@ -3622,7 +3670,7 @@
                 if (!ENABLE_SHELL_TRANSITIONS) {
                     StageCoordinator.this.exitSplitScreen(isMainStage ? mMainStage : mSideStage,
                             EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
-                    mSplitUnsupportedToast.show();
+                    handleUnsupportedSplitStart();
                     return;
                 }
 
@@ -3642,7 +3690,7 @@
                         "app package " + taskInfo.baseActivity.getPackageName()
                         + " does not support splitscreen, or is a controlled activity type"));
                 if (splitScreenVisible) {
-                    mSplitUnsupportedToast.show();
+                    handleUnsupportedSplitStart();
                 }
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index f41bca3..0f3d6ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -177,9 +177,11 @@
     @Override
     @CallSuper
     public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
-        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: task=%d taskParent=%d rootTask=%d",
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: taskId=%d taskParent=%d rootTask=%d "
+                        + "taskActivity=%s",
                 taskInfo.taskId, taskInfo.parentTaskId,
-                mRootTaskInfo != null ? mRootTaskInfo.taskId : -1);
+                mRootTaskInfo != null ? mRootTaskInfo.taskId : -1,
+                taskInfo.baseActivity);
         if (mRootTaskInfo == null) {
             mRootLeash = leash;
             mRootTaskInfo = taskInfo;
@@ -213,6 +215,8 @@
     @Override
     @CallSuper
     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskInfoChanged: taskId=%d taskAct=%s",
+                taskInfo.taskId, taskInfo.baseActivity);
         mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo));
         if (mRootTaskInfo.taskId == taskInfo.taskId) {
             // Inflates split decor view only when the root task is visible.
@@ -260,6 +264,7 @@
     public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskVanished: task=%d", taskInfo.taskId);
         final int taskId = taskInfo.taskId;
+        mWindowDecorViewModel.ifPresent(vm -> vm.onTaskVanished(taskInfo));
         if (mRootTaskInfo.taskId == taskId) {
             mCallbacks.onRootTaskVanished();
             mRootTaskInfo = null;
@@ -309,10 +314,10 @@
     }
 
     void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX,
-            int offsetY, boolean immediately) {
+            int offsetY, boolean immediately, float[] veilColor) {
         if (mSplitDecorManager != null && mRootTaskInfo != null) {
             mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t, offsetX,
-                    offsetY, immediately);
+                    offsetY, immediately, veilColor);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index e419462..e07e1b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -45,6 +45,7 @@
 
 import com.android.internal.R;
 
+import java.io.Closeable;
 import java.util.function.LongConsumer;
 
 /**
@@ -100,7 +101,7 @@
      * Drawable pre-drawing the scaled icon in a separate thread to increase the speed of the
      * final drawing.
      */
-    private static class ImmobileIconDrawable extends Drawable {
+    private static class ImmobileIconDrawable extends Drawable implements Closeable {
         private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG
                 | Paint.FILTER_BITMAP_FLAG);
         private final Matrix mMatrix = new Matrix();
@@ -154,6 +155,16 @@
         public int getOpacity() {
             return 1;
         }
+
+        @Override
+        public void close() {
+            synchronized (mPaint) {
+                if (mIconBitmap != null) {
+                    mIconBitmap.recycle();
+                    mIconBitmap = null;
+                }
+            }
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 4d02ec2..bcacecb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -77,6 +77,7 @@
     private ActivityEmbeddingController mActivityEmbeddingController;
 
     abstract static class MixedTransition {
+        /** Entering Pip from split, breaks split. */
         static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
 
         /** Both the display and split-state (enter/exit) is changing */
@@ -103,6 +104,9 @@
         /** Enter pip from one of the Activity Embedding windows. */
         static final int TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING = 9;
 
+        /** Entering Pip from split, but replace the Pip stage instead of breaking split. */
+        static final int TYPE_ENTER_PIP_REPLACE_FROM_SPLIT = 10;
+
         /** The default animation for this mixed transition. */
         static final int ANIM_TYPE_DEFAULT = 0;
 
@@ -426,7 +430,8 @@
                     ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                             "Converting mixed transition into a keyguard transition");
                     // Consume the original mixed transition
-                    onTransitionConsumed(transition, false, null);
+                    mActiveTransitions.remove(mixed);
+                    mixed.onTransitionConsumed(transition, false, null);
                     return true;
                 } else {
                     // Keyguard handler cannot handle it, process through original mixed
@@ -483,9 +488,11 @@
     // TODO(b/287704263): Remove when split/mixed are reversed.
     public boolean animatePendingEnterPipFromSplit(IBinder transition, TransitionInfo info,
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
-            Transitions.TransitionFinishCallback finishCallback) {
-        final MixedTransition mixed = createDefaultMixedTransition(
-                MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition);
+            Transitions.TransitionFinishCallback finishCallback, boolean replacingPip) {
+        int type = replacingPip
+                ? MixedTransition.TYPE_ENTER_PIP_REPLACE_FROM_SPLIT
+                : MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT;
+        final MixedTransition mixed = createDefaultMixedTransition(type, transition);
         mActiveTransitions.add(mixed);
         Transitions.TransitionFinishCallback callback = wct -> {
             mActiveTransitions.remove(mixed);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
index b028bd6..0ada749 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
@@ -76,7 +76,12 @@
                             info, startTransaction, finishTransaction, finishCallback);
             case TYPE_ENTER_PIP_FROM_SPLIT ->
                     animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
-                            finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
+                            finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler,
+                            /*replacingPip*/ false);
+            case TYPE_ENTER_PIP_REPLACE_FROM_SPLIT ->
+                    animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
+                            finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler,
+                            /*replacingPip*/ true);
             case TYPE_KEYGUARD ->
                     animateKeyguard(this, info, startTransaction, finishTransaction, finishCallback,
                             mKeyguardHandler, mPipHandler);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
index ffc0b76..e8b01b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
@@ -23,11 +23,15 @@
 import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
 import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 
@@ -45,7 +49,8 @@
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback,
             @NonNull Transitions player, @NonNull MixedTransitionHandler mixedHandler,
-            @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) {
+            @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler,
+            boolean replacingPip) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
                 + "entering PIP while Split-Screen is foreground.");
         TransitionInfo.Change pipChange = null;
@@ -99,7 +104,7 @@
             // we need a separate one to send over to launcher.
             SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
             @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED;
-            if (splitHandler.isSplitScreenVisible()) {
+            if (splitHandler.isSplitScreenVisible() && !replacingPip) {
                 // The non-going home case, we could be pip-ing one of the split stages and keep
                 // showing the other
                 for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -115,11 +120,12 @@
                         break;
                     }
                 }
+
+                // Let split update internal state for dismiss.
+                splitHandler.prepareDismissAnimation(topStageToKeep,
+                        EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
+                        finishTransaction);
             }
-            // Let split update internal state for dismiss.
-            splitHandler.prepareDismissAnimation(topStageToKeep,
-                    EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
-                    finishTransaction);
 
             // We are trying to accommodate launcher's close animation which can't handle the
             // divider-bar, so if split-handler is closing the divider-bar, just hide it and
@@ -152,6 +158,44 @@
         return true;
     }
 
+    /**
+     * Check to see if we're only closing split to enter pip or if we're replacing pip with
+     * another task. If we are replacing, this will return the change for the task we are replacing
+     * pip with
+     *
+     * @param info Any number of changes
+     * @param pipChange TransitionInfo.Change indicating the task that is being pipped
+     * @param splitMainStageRootId MainStage's rootTaskInfo's id
+     * @param splitSideStageRootId SideStage's rootTaskInfo's id
+     * @param lastPipSplitStage The last stage that {@param pipChange} was in
+     * @return The change from {@param info} that is replacing the {@param pipChange}, {@code null}
+     *         otherwise
+     */
+    @Nullable
+    public static TransitionInfo.Change getPipReplacingChange(TransitionInfo info,
+            TransitionInfo.Change pipChange, int splitMainStageRootId, int splitSideStageRootId,
+            @SplitScreen.StageType int lastPipSplitStage) {
+        int lastPipParentTask = -1;
+        if (lastPipSplitStage == STAGE_TYPE_MAIN) {
+            lastPipParentTask = splitMainStageRootId;
+        } else if (lastPipSplitStage == STAGE_TYPE_SIDE) {
+            lastPipParentTask = splitSideStageRootId;
+        }
+
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            TransitionInfo.Change change = info.getChanges().get(i);
+            if (change == pipChange || !isOpeningMode(change.getMode())) {
+                // Ignore the change/task that's going into Pip or not opening
+                continue;
+            }
+
+            if (change.getTaskInfo().parentTaskId == lastPipParentTask) {
+                return change;
+            }
+        }
+        return null;
+    }
+
     private static boolean isHomeOpening(@NonNull TransitionInfo.Change change) {
         return change.getTaskInfo() != null
                 && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
index d6e64cf..9fc6702 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -142,7 +142,8 @@
                     && mSplitHandler.getSplitItemPosition(change.getLastParent())
                     != SPLIT_POSITION_UNDEFINED) {
                 return animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
-                        finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
+                        finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler,
+                        /*replacingPip*/ false);
             }
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 9b2922d..6224543 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -31,7 +31,6 @@
 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
-import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
 import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 
@@ -61,6 +60,7 @@
 import android.view.WindowManager;
 import android.window.ITransitionPlayer;
 import android.window.RemoteTransition;
+import android.window.TaskFragmentOrganizer;
 import android.window.TransitionFilter;
 import android.window.TransitionInfo;
 import android.window.TransitionMetrics;
@@ -183,6 +183,13 @@
     /** Transition to resize PiP task. */
     public static final int TRANSIT_RESIZE_PIP = TRANSIT_FIRST_CUSTOM + 16;
 
+    /**
+     * The task fragment drag resize transition used by activity embedding.
+     */
+    public static final int TRANSIT_TASK_FRAGMENT_DRAG_RESIZE =
+            // TRANSIT_FIRST_CUSTOM + 17
+            TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_DRAG_RESIZE;
+
     private final ShellTaskOrganizer mOrganizer;
     private final Context mContext;
     private final ShellExecutor mMainExecutor;
@@ -559,15 +566,15 @@
         final int mode = change.getMode();
         // Put all the OPEN/SHOW on top
         if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
-            if (isOpening
-                    // This is for when an activity launches while a different transition is
-                    // collecting.
-                    || change.hasFlags(FLAG_MOVED_TO_TOP)) {
+            if (isOpening) {
                 // put on top
                 return zSplitLine + numChanges - i;
-            } else {
+            } else if (isClosing) {
                 // put on bottom
                 return zSplitLine - i;
+            } else {
+                // maintain relative ordering (put all changes in the animating layer)
+                return zSplitLine + numChanges - i;
             }
         } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
             if (isOpening) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
index 1897560..6adbe4f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
@@ -49,8 +49,12 @@
 
     public PerfettoTransitionTracer() {
         Producer.init(InitArguments.DEFAULTS);
-        mDataSource.register(
-                new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT));
+        DataSourceParams params =
+                new DataSourceParams.Builder()
+                        .setBufferExhaustedPolicy(
+                                PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)
+                        .build();
+        mDataSource.register(params);
     }
 
     /**
@@ -214,8 +218,6 @@
 
             }
             os.end(mappingsToken);
-
-            ctx.flush();
         });
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 87dc391..bfa163c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -63,6 +63,7 @@
     private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
     private final SyncTransactionQueue mSyncQueue;
     private final Transitions mTransitions;
+    private final ResizeHandleSizeRepository mResizeHandleSizeRepository;
     private TaskOperations mTaskOperations;
 
     private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
@@ -75,7 +76,8 @@
             DisplayController displayController,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             SyncTransactionQueue syncQueue,
-            Transitions transitions) {
+            Transitions transitions,
+            ResizeHandleSizeRepository resizeHandleSizeRepository) {
         mContext = context;
         mMainHandler = mainHandler;
         mMainChoreographer = mainChoreographer;
@@ -84,6 +86,7 @@
         mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
         mSyncQueue = syncQueue;
         mTransitions = transitions;
+        mResizeHandleSizeRepository = resizeHandleSizeRepository;
         if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
             mTaskOperations = new TaskOperations(null, mContext, mSyncQueue);
         }
@@ -119,6 +122,21 @@
     }
 
     @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,
@@ -216,7 +234,8 @@
                         taskSurface,
                         mMainHandler,
                         mMainChoreographer,
-                        mSyncQueue);
+                        mSyncQueue,
+                        mResizeHandleSizeRepository);
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
 
         final FluidResizeTaskPositioner taskPositioner =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 6671391..1be3b02 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -16,11 +16,11 @@
 
 package com.android.wm.shell.windowdecor;
 
-import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
-import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
-import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
+import static com.android.wm.shell.windowdecor.ResizeHandleSizeRepository.getFineResizeCornerPixels;
+import static com.android.wm.shell.windowdecor.ResizeHandleSizeRepository.getLargeResizeCornerPixels;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration;
 import android.app.WindowConfiguration.WindowingMode;
@@ -45,6 +45,8 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
 
+import java.util.function.Consumer;
+
 /**
  * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
  * {@link CaptionWindowDecorViewModel}. The caption bar contains a back button, minimize button,
@@ -58,12 +60,28 @@
     private View.OnClickListener mOnCaptionButtonClickListener;
     private View.OnTouchListener mOnCaptionTouchListener;
     private DragPositioningCallback mDragPositioningCallback;
+    // Listener for handling drag resize events. Will be null if the task cannot be resized.
+    @Nullable
     private DragResizeInputListener mDragResizeListener;
     private DragDetector mDragDetector;
 
     private RelayoutParams mRelayoutParams = new RelayoutParams();
     private final RelayoutResult<WindowDecorLinearLayout> mResult =
             new RelayoutResult<>();
+    private final ResizeHandleSizeRepository mResizeHandleSizeRepository;
+    private final Consumer<ResizeHandleSizeRepository> mResizeHandleSizeChangedFunction =
+            (ResizeHandleSizeRepository sizeRepository) -> {
+                if (mDragResizeListener == null) {
+                    return;
+                }
+                final Resources res = mResult.mRootView.getResources();
+                mDragResizeListener.setGeometry(
+                        new DragResizeWindowGeometry(0 /* taskCornerRadius */,
+                                new Size(mResult.mWidth, mResult.mHeight),
+                                sizeRepository.getResizeEdgeHandlePixels(res),
+                                getFineResizeCornerPixels(res), getLargeResizeCornerPixels(res)),
+                        mDragDetector.getTouchSlop());
+            };
 
     CaptionWindowDecoration(
             Context context,
@@ -73,13 +91,16 @@
             SurfaceControl taskSurface,
             Handler handler,
             Choreographer choreographer,
-            SyncTransactionQueue syncQueue) {
+            SyncTransactionQueue syncQueue,
+            ResizeHandleSizeRepository resizeHandleSizeRepository) {
         super(context, displayController, taskOrganizer, taskInfo, taskSurface,
                 taskInfo.getConfiguration());
 
         mHandler = handler;
         mChoreographer = choreographer;
         mSyncQueue = syncQueue;
+        mResizeHandleSizeRepository = resizeHandleSizeRepository;
+        mResizeHandleSizeRepository.registerSizeChangeFunction(mResizeHandleSizeChangedFunction);
     }
 
     void setCaptionListeners(
@@ -238,10 +259,7 @@
                 .getScaledTouchSlop();
         mDragDetector.setTouchSlop(touchSlop);
 
-        final Resources res = mResult.mRootView.getResources();
-        mDragResizeListener.setGeometry(new DragResizeWindowGeometry(0 /* taskCornerRadius */,
-                new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(res),
-                getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop);
+        mResizeHandleSizeChangedFunction.accept(mResizeHandleSizeRepository);
     }
 
     /**
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 01175f5..10ab13a 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
@@ -35,6 +35,7 @@
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.compatui.AppCompatUtils.isSingleTopActivityTranslucent;
 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 
 import android.annotation.NonNull;
@@ -72,6 +73,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.window.flags.Flags;
 import com.android.wm.shell.R;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
@@ -82,12 +84,12 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
 import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.shared.DesktopModeStatus;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.splitscreen.SplitScreen.StageType;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -147,6 +149,7 @@
             new DesktopModeKeyguardChangeListener();
     private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
     private final DisplayInsetsController mDisplayInsetsController;
+    private final ResizeHandleSizeRepository mResizeHandleSizeRepository;
     private final Region mExclusionRegion = Region.obtain();
     private boolean mInImmersiveMode;
 
@@ -179,7 +182,8 @@
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopTasksController> desktopTasksController,
-            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+            ResizeHandleSizeRepository resizeHandleSizeRepository
     ) {
         this(
                 context,
@@ -200,7 +204,8 @@
                 new InputMonitorFactory(),
                 SurfaceControl.Transaction::new,
                 rootTaskDisplayAreaOrganizer,
-                new SparseArray<>());
+                new SparseArray<>(),
+                resizeHandleSizeRepository);
     }
 
     @VisibleForTesting
@@ -223,7 +228,8 @@
             InputMonitorFactory inputMonitorFactory,
             Supplier<SurfaceControl.Transaction> transactionFactory,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
-            SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId) {
+            SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId,
+            ResizeHandleSizeRepository resizeHandleSizeRepository) {
         mContext = context;
         mMainExecutor = shellExecutor;
         mMainHandler = mainHandler;
@@ -244,6 +250,7 @@
         mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
         mInputManager = mContext.getSystemService(InputManager.class);
         mWindowDecorByTaskId = windowDecorByTaskId;
+        mResizeHandleSizeRepository = resizeHandleSizeRepository;
 
         shellInit.addInitCallback(this::onInit, this);
     }
@@ -276,9 +283,11 @@
             public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
                 if (visible && stage != STAGE_TYPE_UNDEFINED) {
                     DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
-                    if (decor != null && DesktopModeStatus.isEnabled()) {
-                        mDesktopTasksController.moveToSplit(decor.mTaskInfo);
+                    if (decor == null || !DesktopModeStatus.canEnterDesktopMode(mContext)
+                            || decor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+                        return;
                     }
+                    mDesktopTasksController.moveToSplit(decor.mTaskInfo);
                 }
             }
         });
@@ -309,6 +318,22 @@
     }
 
     @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;
+        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "Task Vanished: #%d closed=%b", taskInfo.taskId, closed);
+        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,
@@ -594,7 +619,7 @@
         public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
             final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
             final RunningTaskInfo taskInfo = decoration.mTaskInfo;
-            if (DesktopModeStatus.isEnabled()
+            if (DesktopModeStatus.canEnterDesktopMode(mContext)
                     && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
                 return false;
             }
@@ -771,7 +796,7 @@
      */
     private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
         final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev);
-        if (DesktopModeStatus.isEnabled()) {
+        if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
             if (!mInImmersiveMode && (relevantDecor == null
                     || relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM
                     || mTransitionDragActive)) {
@@ -780,7 +805,7 @@
         }
         handleEventOutsideCaption(ev, relevantDecor);
         // Prevent status bar from reacting to a caption drag.
-        if (DesktopModeStatus.isEnabled()) {
+        if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
             if (mTransitionDragActive) {
                 inputMonitor.pilferPointers();
             }
@@ -838,7 +863,7 @@
                 mDragToDesktopAnimationStartBounds.set(
                         relevantDecor.mTaskInfo.configuration.windowConfiguration.getBounds());
                 boolean dragFromStatusBarAllowed = false;
-                if (DesktopModeStatus.isEnabled()) {
+                if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
                     // In proto2 any full screen or multi-window task can be dragged to
                     // freeform.
                     final int windowingMode = relevantDecor.mTaskInfo.getWindowingMode();
@@ -1013,12 +1038,11 @@
                 && isSingleTopActivityTranslucent(taskInfo)) {
             return false;
         }
-        return DesktopModeStatus.isEnabled()
+        return DesktopModeStatus.canEnterDesktopMode(mContext)
                 && !DesktopWallpaperActivity.isWallpaperTask(taskInfo)
                 && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
                 && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
-                && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop()
-                && DesktopModeStatus.canEnterDesktopMode(mContext);
+                && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop();
     }
 
     private void createWindowDecoration(
@@ -1041,7 +1065,8 @@
                         mMainHandler,
                         mMainChoreographer,
                         mSyncQueue,
-                        mRootTaskDisplayAreaOrganizer);
+                        mRootTaskDisplayAreaOrganizer,
+                        mResizeHandleSizeRepository);
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
 
         final DragPositioningCallback dragPositioningCallback;
@@ -1087,7 +1112,8 @@
     private void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + "DesktopModeWindowDecorViewModel");
-        pw.println(innerPrefix + "DesktopModeStatus=" + DesktopModeStatus.isEnabled());
+        pw.println(innerPrefix + "DesktopModeStatus="
+                + DesktopModeStatus.canEnterDesktopMode(mContext));
         pw.println(innerPrefix + "mTransitionDragActive=" + mTransitionDragActive);
         pw.println(innerPrefix + "mEventReceiversByDisplay=" + mEventReceiversByDisplay);
         pw.println(innerPrefix + "mWindowDecorByTaskId=" + mWindowDecorByTaskId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 8c6bc73..18c3113 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -24,11 +24,11 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
-import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
-import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
-import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
+import static com.android.wm.shell.windowdecor.ResizeHandleSizeRepository.getFineResizeCornerPixels;
+import static com.android.wm.shell.windowdecor.ResizeHandleSizeRepository.getLargeResizeCornerPixels;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.WindowConfiguration.WindowingMode;
 import android.content.Context;
@@ -60,14 +60,15 @@
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.IconProvider;
+import com.android.window.flags.Flags;
 import com.android.wm.shell.R;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.shared.DesktopModeStatus;
 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
 import com.android.wm.shell.windowdecor.viewholder.DesktopModeAppControlsWindowDecorationViewHolder;
 import com.android.wm.shell.windowdecor.viewholder.DesktopModeFocusedWindowDecorationViewHolder;
@@ -75,6 +76,7 @@
 
 import kotlin.Unit;
 
+import java.util.function.Function;
 import java.util.function.Supplier;
 
 /**
@@ -96,6 +98,8 @@
     private View.OnLongClickListener mOnCaptionLongClickListener;
     private View.OnGenericMotionListener mOnCaptionGenericMotionListener;
     private DragPositioningCallback mDragPositioningCallback;
+    // Listener for handling drag resize events. Will be null if the task cannot be resized.
+    @Nullable
     private DragResizeInputListener mDragResizeListener;
     private DragDetector mDragDetector;
 
@@ -110,12 +114,27 @@
 
     private ResizeVeil mResizeVeil;
     private Bitmap mAppIconBitmap;
+    private Bitmap mResizeVeilBitmap;
+
     private CharSequence mAppName;
 
     private ExclusionRegionListener mExclusionRegionListener;
 
     private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
 
+    private final ResizeHandleSizeRepository mResizeHandleSizeRepository;
+    private final Function<ResizeHandleSizeRepository, Boolean> mResizeHandleSizeChangedFunction =
+            (ResizeHandleSizeRepository sizeRepository) -> {
+                final Resources res = mResult.mRootView.getResources();
+                return mDragResizeListener == null || mDragResizeListener.setGeometry(
+                        new DragResizeWindowGeometry(mRelayoutParams.mCornerRadius,
+                                new Size(mResult.mWidth, mResult.mHeight),
+                                sizeRepository.getResizeEdgeHandlePixels(res),
+                                getFineResizeCornerPixels(res),
+                                getLargeResizeCornerPixels(res)),
+                        mDragDetector.getTouchSlop());
+            };
+
     DesktopModeWindowDecoration(
             Context context,
             DisplayController displayController,
@@ -126,12 +145,13 @@
             Handler handler,
             Choreographer choreographer,
             SyncTransactionQueue syncQueue,
-            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+            ResizeHandleSizeRepository resizeHandleSizeRepository) {
         this (context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
                 handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer,
-                SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
-                WindowContainerTransaction::new, SurfaceControl::new,
-                new SurfaceControlViewHostFactory() {});
+                resizeHandleSizeRepository, SurfaceControl.Builder::new,
+                SurfaceControl.Transaction::new, WindowContainerTransaction::new,
+                SurfaceControl::new, new SurfaceControlViewHostFactory() {});
     }
 
     DesktopModeWindowDecoration(
@@ -145,6 +165,7 @@
             Choreographer choreographer,
             SyncTransactionQueue syncQueue,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+            ResizeHandleSizeRepository resizeHandleSizeRepository,
             Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
             Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
             Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
@@ -158,6 +179,9 @@
         mChoreographer = choreographer;
         mSyncQueue = syncQueue;
         mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+        mResizeHandleSizeRepository = resizeHandleSizeRepository;
+        mResizeHandleSizeRepository.registerSizeChangeFunction(
+                mResizeHandleSizeChangedFunction::apply);
     }
 
     void setCaptionListeners(
@@ -303,11 +327,7 @@
 
         // If either task geometry or position have changed, update this task's
         // exclusion region listener
-        final Resources res = mResult.mRootView.getResources();
-        if (mDragResizeListener.setGeometry(
-                new DragResizeWindowGeometry(mRelayoutParams.mCornerRadius,
-                        new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(res),
-                        getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop)
+        if (mResizeHandleSizeChangedFunction.apply(mResizeHandleSizeRepository)
                 || !mTaskInfo.positionInParent.equals(mPositionInParent)) {
             updateExclusionRegion();
         }
@@ -330,13 +350,16 @@
             boolean applyStartTransactionOnDraw,
             boolean shouldSetTaskPositionAndCrop) {
         final int captionLayoutId = getDesktopModeWindowDecorLayoutId(taskInfo.getWindowingMode());
+        final boolean isAppHeader =
+                captionLayoutId == R.layout.desktop_mode_app_controls_window_decor;
+        final boolean isAppHandle = captionLayoutId == R.layout.desktop_mode_focused_window_decor;
         relayoutParams.reset();
         relayoutParams.mRunningTaskInfo = taskInfo;
         relayoutParams.mLayoutResId = captionLayoutId;
         relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode());
         relayoutParams.mCaptionWidthId = getCaptionWidthId(relayoutParams.mLayoutResId);
 
-        if (captionLayoutId == R.layout.desktop_mode_app_controls_window_decor) {
+        if (isAppHeader) {
             if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
                 // If the app is requesting to customize the caption bar, allow input to fall
                 // through to the windows below so that the app can respond to input events on
@@ -357,7 +380,7 @@
             controlsElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_margin_end;
             controlsElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.END;
             relayoutParams.mOccludingCaptionElements.add(controlsElement);
-        } else if (captionLayoutId == R.layout.desktop_mode_focused_window_decor) {
+        } else if (isAppHandle) {
             // The focused decor (fullscreen/split) does not need to handle input because input in
             // the App Handle is handled by the InputMonitor in DesktopModeWindowDecorViewModel.
             relayoutParams.mInputFeatures
@@ -370,19 +393,25 @@
         }
         relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
         relayoutParams.mSetTaskPositionAndCrop = shouldSetTaskPositionAndCrop;
-        // The configuration used to lay out the window decoration. The system context's config is
-        // used when the task density has been overridden to a custom density so that the resources
-        // and views of the decoration aren't affected and match the rest of the System UI, if not
-        // then just use the task's configuration. A copy is made instead of using the original
-        // reference so that the configuration isn't mutated on config changes and diff checks can
-        // be made in WindowDecoration#relayout using the pre/post-relayout configuration.
-        // See b/301119301.
+
+        // The configuration used to layout the window decoration. A copy is made instead of using
+        // the original reference so that the configuration isn't mutated on config changes and
+        // diff checks can be made in WindowDecoration#relayout using the pre/post-relayout
+        // configuration. See b/301119301.
         // TODO(b/301119301): consider moving the config data needed for diffs to relayout params
         // instead of using a whole Configuration as a parameter.
         final Configuration windowDecorConfig = new Configuration();
-        windowDecorConfig.setTo(DesktopTasksController.isDesktopDensityOverrideSet()
-                ? context.getResources().getConfiguration() // Use system context.
-                : taskInfo.configuration); // Use task configuration.
+        if (Flags.enableAppHeaderWithTaskDensity() && isAppHeader) {
+            // Should match the density of the task. The task may have had its density overridden
+            // to be different that SysUI's.
+            windowDecorConfig.setTo(taskInfo.configuration);
+        } else if (DesktopTasksController.isDesktopDensityOverrideSet()) {
+            // The task has had its density overridden, but keep using the system's density to
+            // layout the header.
+            windowDecorConfig.setTo(context.getResources().getConfiguration());
+        } else {
+            windowDecorConfig.setTo(taskInfo.configuration);
+        }
         relayoutParams.mWindowDecorConfig = windowDecorConfig;
 
         if (DesktopModeStatus.useRoundedCorners()) {
@@ -468,11 +497,15 @@
             PackageManager pm = mContext.getApplicationContext().getPackageManager();
             final IconProvider provider = new IconProvider(mContext);
             final Drawable appIconDrawable = provider.getIcon(activityInfo);
-            final Resources resources = mContext.getResources();
-            final BaseIconFactory factory = new BaseIconFactory(mContext,
-                    resources.getDisplayMetrics().densityDpi,
-                    resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius));
-            mAppIconBitmap = factory.createScaledBitmap(appIconDrawable, MODE_DEFAULT);
+            final BaseIconFactory headerIconFactory = createIconFactory(mContext,
+                    R.dimen.desktop_mode_caption_icon_radius);
+            mAppIconBitmap = headerIconFactory.createScaledBitmap(appIconDrawable, MODE_DEFAULT);
+
+            final BaseIconFactory resizeVeilIconFactory = createIconFactory(mContext,
+                    R.dimen.desktop_mode_resize_veil_icon_size);
+            mResizeVeilBitmap = resizeVeilIconFactory
+                    .createScaledBitmap(appIconDrawable, MODE_DEFAULT);
+
             final ApplicationInfo applicationInfo = activityInfo.applicationInfo;
             mAppName = pm.getApplicationLabel(applicationInfo);
         } finally {
@@ -480,6 +513,13 @@
         }
     }
 
+    private BaseIconFactory createIconFactory(Context context, int dimensions) {
+        final Resources resources = context.getResources();
+        final int densityDpi = resources.getDisplayMetrics().densityDpi;
+        final int iconSize = resources.getDimensionPixelSize(dimensions);
+        return new BaseIconFactory(context, densityDpi, iconSize);
+    }
+
     private void closeDragResizeListener() {
         if (mDragResizeListener == null) {
             return;
@@ -495,7 +535,7 @@
     private void createResizeVeilIfNeeded() {
         if (mResizeVeil != null) return;
         loadAppInfoIfNeeded();
-        mResizeVeil = new ResizeVeil(mContext, mDisplayController, mAppIconBitmap, mTaskInfo,
+        mResizeVeil = new ResizeVeil(mContext, mDisplayController, mResizeVeilBitmap, mTaskInfo,
                 mTaskSurface, mSurfaceControlTransactionSupplier);
     }
 
@@ -638,7 +678,7 @@
                 .setOnClickListener(mOnCaptionButtonClickListener)
                 .setOnTouchListener(mOnCaptionTouchListener)
                 .setLayoutId(mRelayoutParams.mLayoutResId)
-                .setWindowingButtonsVisible(DesktopModeStatus.isEnabled())
+                .setWindowingButtonsVisible(DesktopModeStatus.canEnterDesktopMode(mContext))
                 .setCaptionHeight(mResult.mCaptionHeight)
                 .build();
         mWindowDecorViewHolder.onHandleMenuOpened();
@@ -923,7 +963,8 @@
                 Handler handler,
                 Choreographer choreographer,
                 SyncTransactionQueue syncQueue,
-                RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+                RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+                ResizeHandleSizeRepository resizeHandleSizeRepository) {
             final Configuration windowDecorConfig =
                     DesktopTasksController.isDesktopDensityOverrideSet()
                     ? context.getResources().getConfiguration() // Use system context
@@ -938,7 +979,8 @@
                     handler,
                     choreographer,
                     syncQueue,
-                    rootTaskDisplayAreaOrganizer);
+                    rootTaskDisplayAreaOrganizer,
+                    resizeHandleSizeRepository);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
index da26898..a3b0a71 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
@@ -119,6 +119,10 @@
         mTouchSlop = touchSlop;
     }
 
+    int getTouchSlop() {
+        return mTouchSlop;
+    }
+
     private void resetState() {
         mIsDragEvent = false;
         mInputDownPoint.set(0, 0);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 9624d46..5379ca6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -24,7 +24,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
 
-import static com.android.input.flags.Flags.enablePointerChoreographer;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
@@ -54,6 +54,7 @@
 import android.view.WindowManagerGlobal;
 import android.window.InputTransferToken;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 
@@ -399,12 +400,17 @@
                         float rawX = e.getRawX(0);
                         float rawY = e.getRawY(0);
                         int ctrlType = mDragResizeWindowGeometry.calculateCtrlType(isTouch, x, y);
+                        ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+                                "%s: Handling action down, update ctrlType to %d", TAG, ctrlType);
                         mDragStartTaskBounds = mCallback.onDragPositioningStart(ctrlType,
                                 rawX, rawY);
                         // Increase the input sink region to cover the whole screen; this is to
                         // prevent input and focus from going to other tasks during a drag resize.
                         updateInputSinkRegionForDrag(mDragStartTaskBounds);
                         result = true;
+                    } else {
+                        ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+                                "%s: Handling action down, but ignore event", TAG);
                     }
                     break;
                 }
@@ -499,12 +505,10 @@
             // where views in the task can receive input events because we can't set touch regions
             // of input sinks to have rounded corners.
             if (mLastCursorType != cursorType || cursorType != PointerIcon.TYPE_DEFAULT) {
-                if (enablePointerChoreographer()) {
-                    mInputManager.setPointerIcon(PointerIcon.getSystemIcon(mContext, cursorType),
-                            displayId, deviceId, pointerId, mInputChannel.getToken());
-                } else {
-                    mInputManager.setPointerIconType(cursorType);
-                }
+                ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: update pointer icon from %d to %d",
+                        TAG, mLastCursorType, cursorType);
+                mInputManager.setPointerIcon(PointerIcon.getSystemIcon(mContext, cursorType),
+                        displayId, deviceId, pointerId, mInputChannel.getToken());
                 mLastCursorType = cursorType;
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
index eafb569..80d60d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
@@ -26,14 +26,14 @@
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED;
 
 import android.annotation.NonNull;
-import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.util.Size;
 import android.view.MotionEvent;
 
-import com.android.wm.shell.R;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import java.util.Objects;
 
@@ -41,6 +41,11 @@
  * Geometry for a drag resize region for a particular window.
  */
 final class DragResizeWindowGeometry {
+    // TODO(b/337264971) clean up when no longer needed
+    @VisibleForTesting static final boolean DEBUG = true;
+    // The additional width to apply to edge resize bounds just for logging when a touch is
+    // close.
+    @VisibleForTesting static final int EDGE_DEBUG_BUFFER = 15;
     private final int mTaskCornerRadius;
     private final Size mTaskSize;
     // The size of the handle applied to the edges of the window, for the user to drag resize.
@@ -51,11 +56,14 @@
     // The task corners to permit drag resizing with a fine input, such as stylus or cursor.
     private final @NonNull TaskCorners mFineTaskCorners;
     // The bounds for each edge drag region, which can resize the task in one direction.
-    private final @NonNull Rect mTopEdgeBounds;
-    private final @NonNull Rect mLeftEdgeBounds;
-    private final @NonNull Rect mRightEdgeBounds;
-    private final @NonNull Rect mBottomEdgeBounds;
+    private final @NonNull TaskEdges mTaskEdges;
+    // Extra-large edge bounds for logging to help debug when an edge resize is ignored.
+    private final @Nullable TaskEdges mDebugTaskEdges;
 
+    /**
+     * Constructs an instance representing the drag resize touch input regions, where all sizes
+     * are represented in pixels.
+     */
     DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize,
             int resizeHandleThickness, int fineCornerSize, int largeCornerSize) {
         mTaskCornerRadius = taskCornerRadius;
@@ -66,51 +74,12 @@
         mFineTaskCorners = new TaskCorners(mTaskSize, fineCornerSize);
 
         // Save touch areas for each edge.
-        mTopEdgeBounds = new Rect(
-                -mResizeHandleThickness,
-                -mResizeHandleThickness,
-                mTaskSize.getWidth() + mResizeHandleThickness,
-                0);
-        mLeftEdgeBounds = new Rect(
-                -mResizeHandleThickness,
-                0,
-                0,
-                mTaskSize.getHeight());
-        mRightEdgeBounds = new Rect(
-                mTaskSize.getWidth(),
-                0,
-                mTaskSize.getWidth() + mResizeHandleThickness,
-                mTaskSize.getHeight());
-        mBottomEdgeBounds = new Rect(
-                -mResizeHandleThickness,
-                mTaskSize.getHeight(),
-                mTaskSize.getWidth() + mResizeHandleThickness,
-                mTaskSize.getHeight() + mResizeHandleThickness);
-    }
-
-    /**
-     * Returns the resource value to use for the resize handle on the edge of the window.
-     */
-    static int getResizeEdgeHandleSize(@NonNull Resources res) {
-        return enableWindowingEdgeDragResize()
-                ? res.getDimensionPixelSize(R.dimen.desktop_mode_edge_handle)
-                : res.getDimensionPixelSize(R.dimen.freeform_resize_handle);
-    }
-
-    /**
-     * Returns the resource value to use for course input, such as touch, that benefits from a large
-     * square on each of the window's corners.
-     */
-    static int getLargeResizeCornerSize(@NonNull Resources res) {
-        return res.getDimensionPixelSize(R.dimen.desktop_mode_corner_resize_large);
-    }
-
-    /**
-     * Returns the resource value to use for fine input, such as stylus, that can use a smaller
-     * square on each of the window's corners.
-     */
-    static int getFineResizeCornerSize(@NonNull Resources res) {
-        return res.getDimensionPixelSize(R.dimen.freeform_resize_corner);
+        mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness);
+        if (DEBUG) {
+            mDebugTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness + EDGE_DEBUG_BUFFER);
+        } else {
+            mDebugTaskEdges = null;
+        }
     }
 
     /**
@@ -127,10 +96,13 @@
      */
     void union(@NonNull Region region) {
         // Apply the edge resize regions.
-        region.union(mTopEdgeBounds);
-        region.union(mLeftEdgeBounds);
-        region.union(mRightEdgeBounds);
-        region.union(mBottomEdgeBounds);
+        if (inDebugMode()) {
+            // Use the larger edge sizes if we are debugging, to be able to log if we ignored a
+            // touch due to the size of the edge region.
+            mDebugTaskEdges.union(region);
+        } else {
+            mTaskEdges.union(region);
+        }
 
         if (enableWindowingEdgeDragResize()) {
             // Apply the corners as well for the larger corners, to ensure we capture all possible
@@ -216,6 +188,10 @@
 
     @DragPositioningCallback.CtrlType
     private int calculateEdgeResizeCtrlType(float x, float y) {
+        if (inDebugMode() && (mDebugTaskEdges.contains((int) x, (int) y)
+                    && !mTaskEdges.contains((int) x, (int) y))) {
+            return CTRL_TYPE_UNDEFINED;
+        }
         int ctrlType = CTRL_TYPE_UNDEFINED;
         // mTaskCornerRadius is only used in comparing with corner regions. Comparisons with
         // sides will use the bounds specified in setGeometry and not go into task bounds.
@@ -306,10 +282,9 @@
                 && this.mResizeHandleThickness == other.mResizeHandleThickness
                 && this.mFineTaskCorners.equals(other.mFineTaskCorners)
                 && this.mLargeTaskCorners.equals(other.mLargeTaskCorners)
-                && this.mTopEdgeBounds.equals(other.mTopEdgeBounds)
-                && this.mLeftEdgeBounds.equals(other.mLeftEdgeBounds)
-                && this.mRightEdgeBounds.equals(other.mRightEdgeBounds)
-                && this.mBottomEdgeBounds.equals(other.mBottomEdgeBounds);
+                && (inDebugMode()
+                        ? this.mDebugTaskEdges.equals(other.mDebugTaskEdges)
+                        : this.mTaskEdges.equals(other.mTaskEdges));
     }
 
     @Override
@@ -320,10 +295,11 @@
                 mResizeHandleThickness,
                 mFineTaskCorners,
                 mLargeTaskCorners,
-                mTopEdgeBounds,
-                mLeftEdgeBounds,
-                mRightEdgeBounds,
-                mBottomEdgeBounds);
+                (inDebugMode() ? mDebugTaskEdges : mTaskEdges));
+    }
+
+    private boolean inDebugMode() {
+        return DEBUG && mDebugTaskEdges != null;
     }
 
     /**
@@ -431,4 +407,92 @@
                     mRightBottomCornerBounds);
         }
     }
+
+    /**
+     * Representation of the drag resize regions at the edges of the window.
+     */
+    private static class TaskEdges {
+        private final @NonNull Rect mTopEdgeBounds;
+        private final @NonNull Rect mLeftEdgeBounds;
+        private final @NonNull Rect mRightEdgeBounds;
+        private final @NonNull Rect mBottomEdgeBounds;
+        private final @NonNull Region mRegion;
+
+        private TaskEdges(@NonNull Size taskSize, int resizeHandleThickness) {
+            // Save touch areas for each edge.
+            mTopEdgeBounds = new Rect(
+                    -resizeHandleThickness,
+                    -resizeHandleThickness,
+                    taskSize.getWidth() + resizeHandleThickness,
+                    0);
+            mLeftEdgeBounds = new Rect(
+                    -resizeHandleThickness,
+                    0,
+                    0,
+                    taskSize.getHeight());
+            mRightEdgeBounds = new Rect(
+                    taskSize.getWidth(),
+                    0,
+                    taskSize.getWidth() + resizeHandleThickness,
+                    taskSize.getHeight());
+            mBottomEdgeBounds = new Rect(
+                    -resizeHandleThickness,
+                    taskSize.getHeight(),
+                    taskSize.getWidth() + resizeHandleThickness,
+                    taskSize.getHeight() + resizeHandleThickness);
+
+            mRegion = new Region();
+            mRegion.union(mTopEdgeBounds);
+            mRegion.union(mLeftEdgeBounds);
+            mRegion.union(mRightEdgeBounds);
+            mRegion.union(mBottomEdgeBounds);
+        }
+
+        /**
+         * Returns {@code true} if the edges contain the given point.
+         */
+        private boolean contains(int x, int y) {
+            return mRegion.contains(x, y);
+        }
+
+        /**
+         * Updates the region to include all four corners.
+         */
+        private void union(Region region) {
+            region.union(mTopEdgeBounds);
+            region.union(mLeftEdgeBounds);
+            region.union(mRightEdgeBounds);
+            region.union(mBottomEdgeBounds);
+        }
+
+        @Override
+        public String toString() {
+            return "TaskEdges for the"
+                    + " top " + mTopEdgeBounds
+                    + " left " + mLeftEdgeBounds
+                    + " right " + mRightEdgeBounds
+                    + " bottom " + mBottomEdgeBounds;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null) return false;
+            if (this == obj) return true;
+            if (!(obj instanceof TaskEdges other)) return false;
+
+            return this.mTopEdgeBounds.equals(other.mTopEdgeBounds)
+                    && this.mLeftEdgeBounds.equals(other.mLeftEdgeBounds)
+                    && this.mRightEdgeBounds.equals(other.mRightEdgeBounds)
+                    && this.mBottomEdgeBounds.equals(other.mBottomEdgeBounds);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(
+                    mTopEdgeBounds,
+                    mLeftEdgeBounds,
+                    mRightEdgeBounds,
+                    mBottomEdgeBounds);
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepository.kt
new file mode 100644
index 0000000..be7a301
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepository.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.content.res.Resources
+import com.android.window.flags.Flags.enableWindowingEdgeDragResize
+import com.android.wm.shell.R
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.util.KtProtoLog
+import java.util.function.Consumer
+
+/** Repository for desktop mode drag resize handle sizes. */
+class ResizeHandleSizeRepository {
+    private val TAG = "ResizeHandleSizeRepository"
+    private var edgeResizeHandleSizePixels: Int? = null
+    private var sizeChangeFunctions: MutableList<Consumer<ResizeHandleSizeRepository>> =
+        mutableListOf()
+
+    /**
+     * Resets the window edge resize handle size if necessary.
+     */
+    fun resetResizeEdgeHandlePixels() {
+        if (enableWindowingEdgeDragResize() && edgeResizeHandleSizePixels != null) {
+            edgeResizeHandleSizePixels = null
+            applyToAll()
+        }
+    }
+
+    /**
+     * Sets the window edge resize handle to the given size in pixels.
+     */
+    fun setResizeEdgeHandlePixels(sizePixels: Int) {
+        if (enableWindowingEdgeDragResize()) {
+            KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "$TAG: Set edge handle size to $sizePixels")
+            if (edgeResizeHandleSizePixels != null && edgeResizeHandleSizePixels == sizePixels) {
+                // Skip updating since override is the same size
+                return
+            }
+            edgeResizeHandleSizePixels = sizePixels
+            applyToAll()
+        } else {
+            KtProtoLog.d(
+                WM_SHELL_DESKTOP_MODE,
+                "$TAG: Can't set edge handle size to $sizePixels since " +
+                    "enable_windowing_edge_drag_resize disabled"
+            )
+        }
+    }
+
+    /**
+     * Returns the resource value, in pixels, to use for the resize handle on the edge of the
+     * window.
+     */
+    fun getResizeEdgeHandlePixels(res: Resources): Int {
+        try {
+            return if (enableWindowingEdgeDragResize()) {
+                val resPixelSize = res.getDimensionPixelSize(R.dimen.desktop_mode_edge_handle)
+                val size = edgeResizeHandleSizePixels ?: resPixelSize
+                KtProtoLog.d(
+                    WM_SHELL_DESKTOP_MODE,
+                    "$TAG: Get edge handle size of $size from (vs base value $resPixelSize)"
+                )
+                size
+            } else {
+                KtProtoLog.d(
+                    WM_SHELL_DESKTOP_MODE,
+                    "$TAG: Get edge handle size from freeform since flag is disabled"
+                )
+                res.getDimensionPixelSize(R.dimen.freeform_resize_handle)
+            }
+        } catch (e: Resources.NotFoundException) {
+            KtProtoLog.e(WM_SHELL_DESKTOP_MODE, "$TAG: Unable to get edge handle size", e)
+            return 0
+        }
+    }
+
+    /** Register function to run when the resize handle size changes. */
+    fun registerSizeChangeFunction(function: Consumer<ResizeHandleSizeRepository>) {
+        sizeChangeFunctions.add(function)
+    }
+
+    private fun applyToAll() {
+        for (f in sizeChangeFunctions) {
+            f.accept(this)
+        }
+    }
+
+    companion object {
+        private val TAG = "ResizeHandleSizeRepositoryCompanion"
+
+        /**
+         * Returns the resource value in pixels to use for course input, such as touch, that
+         * benefits from a large square on each of the window's corners.
+         */
+        @JvmStatic
+        fun getLargeResizeCornerPixels(res: Resources): Int {
+            return try {
+                res.getDimensionPixelSize(R.dimen.desktop_mode_corner_resize_large)
+            } catch (e: Resources.NotFoundException) {
+                KtProtoLog.e(WM_SHELL_DESKTOP_MODE, "$TAG: Unable to get large corner size", e)
+                0
+            }
+        }
+
+        /**
+         * Returns the resource value, in pixels, to use for fine input, such as stylus, that can
+         * use a smaller square on each of the window's corners.
+         */
+        @JvmStatic
+        fun getFineResizeCornerPixels(res: Resources): Int {
+            return try {
+                res.getDimensionPixelSize(R.dimen.freeform_resize_corner)
+            } catch (e: Resources.NotFoundException) {
+                KtProtoLog.e(WM_SHELL_DESKTOP_MODE, "$TAG: Unable to get fine corner size", e)
+                0
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index 01a6012..1563259 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -67,6 +67,14 @@
     void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo);
 
     /**
+     * Notifies a task has vanished, which can mean that the task changed windowing mode or was
+     * removed.
+     *
+     * @param taskInfo the task info of the task
+     */
+    void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo);
+
+    /**
      * Notifies a transition is about to start about the given task to give the window decoration a
      * chance to prepare for this transition. Unlike {@link #onTaskInfoChanged}, this method creates
      * a window decoration if one does not exist but is required.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index de6c035..5418254 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -52,7 +52,7 @@
 
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.DesktopModeStatus;
 import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement;
 
 import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
index a2293d5..ec20471 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.windowdecor.extension
 
 import android.app.TaskInfo
-import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
 import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS
 import android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND
@@ -36,6 +35,3 @@
 
 val TaskInfo.isFullscreen: Boolean
     get() = windowingMode == WINDOWING_MODE_FULLSCREEN
-
-val TaskInfo.isFreeform: Boolean
-    get() = windowingMode == WINDOWING_MODE_FREEFORM
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index 0f24bb5..b8a19ad 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -13,3 +13,5 @@
 pbdr@google.com
 tkachenkoi@google.com
 mpodolian@google.com
+jeremysim@google.com
+peanutbutter@google.com
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
index 4dd14f4..f69a90c 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
@@ -91,6 +91,7 @@
                 value="trace_config.textproto"
         />
         <option name="instrumentation-arg" key="per_run" value="true"/>
+        <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
     </test>
     <!-- Needed for pulling the collected trace config on to the host -->
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
index 5c86a38..b76d065 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
@@ -91,6 +91,7 @@
                 value="trace_config.textproto"
         />
         <option name="instrumentation-arg" key="per_run" value="true"/>
+        <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
     </test>
     <!-- Needed for pulling the collected trace config on to the host -->
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
index bc486c2..984abf8 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
@@ -32,7 +32,7 @@
 /**
  * Test launching a new activity from bubble.
  *
- * To run this test: `atest WMShellFlickerTests:MultiBubblesScreen`
+ * To run this test: `atest WMShellFlickerTestsBubbles:ChangeActiveActivityFromBubbleTest`
  *
  * Actions:
  * ```
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
index 2a9b107..886b70c 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
@@ -35,7 +35,7 @@
 /**
  * Test launching a new activity from bubble.
  *
- * To run this test: `atest WMShellFlickerTests:DismissBubbleScreen`
+ * To run this test: `atest WMShellFlickerTestsBubbles:DragToDismissBubbleScreenTest`
  *
  * Actions:
  * ```
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
index 9ef49c1..2ee53f4 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
@@ -38,7 +38,7 @@
 /**
  * Test launching a new activity from bubble.
  *
- * To run this test: `atest WMShellFlickerTests:OpenActivityFromBubbleOnLocksreenTest`
+ * To run this test: `atest WMShellFlickerTestsBubbles:OpenActivityFromBubbleOnLocksreenTest`
  *
  * Actions:
  * ```
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
index ef7fbfb..463fe0e 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
@@ -29,7 +29,7 @@
 /**
  * Test launching a new activity from bubble.
  *
- * To run this test: `atest WMShellFlickerTests:ExpandBubbleScreen`
+ * To run this test: `atest WMShellFlickerTestsBubbles:OpenActivityFromBubbleTest`
  *
  * Actions:
  * ```
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
index 87224b15..8df5056 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
@@ -29,7 +29,7 @@
 /**
  * Test creating a bubble notification
  *
- * To run this test: `atest WMShellFlickerTests:LaunchBubbleScreen`
+ * To run this test: `atest WMShellFlickerTestsBubbles:SendBubbleNotificationTest`
  *
  * Actions:
  * ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
index aa70c09..041978c 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
@@ -91,6 +91,7 @@
                 value="trace_config.textproto"
         />
         <option name="instrumentation-arg" key="per_run" value="true"/>
+        <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
     </test>
     <!-- Needed for pulling the collected trace config on to the host -->
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
index c7c804f..a66dfb4 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml
@@ -91,6 +91,7 @@
                 value="trace_config.textproto"
         />
         <option name="instrumentation-arg" key="per_run" value="true"/>
+        <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
     </test>
     <!-- Needed for pulling the collected trace config on to the host -->
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
index 17cace0..d485b82 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
@@ -21,6 +21,7 @@
 import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways
 import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart
 import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd
+import android.tools.flicker.assertors.assertions.AppWindowIsVisibleAlways
 import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd
 import android.tools.flicker.assertors.assertions.AppWindowOnTopAtStart
 import android.tools.flicker.assertors.assertions.AppWindowRemainInsideDisplayBounds
@@ -133,9 +134,8 @@
                     }
                 ),
                 assertions =
-                AssertionTemplates.COMMON_ASSERTIONS +
                         listOf(
-                            AppLayerIsVisibleAlways(Components.DESKTOP_MODE_APP),
+                            AppWindowIsVisibleAlways(Components.DESKTOP_MODE_APP),
                             AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP),
                             AppWindowRemainInsideDisplayBounds(Components.DESKTOP_MODE_APP),
                         ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml
index 214bdfa..85715db 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml
@@ -91,6 +91,7 @@
                 value="trace_config.textproto"
         />
         <option name="instrumentation-arg" key="per_run" value="true"/>
+        <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
     </test>
     <!-- Needed for pulling the collected trace config on to the host -->
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index 2ac72af..ea522cd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -20,6 +20,8 @@
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 
+import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE;
+
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -100,6 +102,20 @@
     }
 
     @Test
+    public void testTransitionTypeDragResize() {
+        final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TASK_FRAGMENT_DRAG_RESIZE, 0)
+                .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY))
+                .build();
+        final Animator animator = mAnimRunner.createAnimator(
+                info, mStartTransaction, mFinishTransaction,
+                () -> mFinishCallback.onTransitionFinished(null /* wct */),
+                new ArrayList());
+
+        // The animation should be empty when it is a jump cut for drag resize.
+        assertEquals(0, animator.getDuration());
+    }
+
+    @Test
     public void testInvalidCustomAnimation() {
         final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
                 .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index f99b4b2..f6f3aa4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -120,7 +120,7 @@
     private TestableContentResolver mContentResolver;
     private TestableLooper mTestableLooper;
 
-    private CrossActivityBackAnimation mCrossActivityBackAnimation;
+    private DefaultCrossActivityBackAnimation mDefaultCrossActivityBackAnimation;
     private CrossTaskBackAnimation mCrossTaskBackAnimation;
     private ShellBackAnimationRegistry mShellBackAnimationRegistry;
 
@@ -135,13 +135,14 @@
                 ANIMATION_ENABLED);
         mTestableLooper = TestableLooper.get(this);
         mShellInit = spy(new ShellInit(mShellExecutor));
-        mCrossActivityBackAnimation = new CrossActivityBackAnimation(mContext, mAnimationBackground,
-                mRootTaskDisplayAreaOrganizer);
+        mDefaultCrossActivityBackAnimation = new DefaultCrossActivityBackAnimation(mContext,
+                mAnimationBackground, mRootTaskDisplayAreaOrganizer);
         mCrossTaskBackAnimation = new CrossTaskBackAnimation(mContext, mAnimationBackground);
         mShellBackAnimationRegistry =
-                new ShellBackAnimationRegistry(mCrossActivityBackAnimation, mCrossTaskBackAnimation,
-                        /* dialogCloseAnimation= */ null,
-                        new CustomizeActivityAnimation(mContext, mAnimationBackground),
+                new ShellBackAnimationRegistry(mDefaultCrossActivityBackAnimation,
+                        mCrossTaskBackAnimation, /* dialogCloseAnimation= */ null,
+                        new CustomCrossActivityBackAnimation(mContext, mAnimationBackground,
+                                mRootTaskDisplayAreaOrganizer),
                         /* defaultBackToHomeAnimation= */ null);
         mController =
                 new BackAnimationController(
@@ -582,7 +583,7 @@
     @Test
     public void testBackToActivity() throws RemoteException {
         verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
-                mCrossActivityBackAnimation.getRunner());
+                mDefaultCrossActivityBackAnimation.getRunner());
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
new file mode 100644
index 0000000..8bf0111
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
@@ -0,0 +1,264 @@
+/*
+ * 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.f
+ */
+package com.android.wm.shell.back
+
+import android.app.ActivityManager
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.AppCompatTaskInfo
+import android.app.WindowConfiguration
+import android.graphics.Color
+import android.graphics.Point
+import android.graphics.Rect
+import android.os.RemoteException
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.Choreographer
+import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import android.view.animation.Animation
+import android.window.BackEvent
+import android.window.BackMotionEvent
+import android.window.BackNavigationInfo
+import androidx.test.filters.SmallTest
+import com.android.internal.policy.TransitionAnimation
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTestCase
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import junit.framework.TestCase.assertEquals
+import org.junit.Assert
+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.anyFloat
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class CustomCrossActivityBackAnimationTest : ShellTestCase() {
+    @Mock private lateinit var backAnimationBackground: BackAnimationBackground
+    @Mock private lateinit var mockCloseAnimation: Animation
+    @Mock private lateinit var mockOpenAnimation: Animation
+    @Mock private lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+    @Mock private lateinit var transitionAnimation: TransitionAnimation
+    @Mock private lateinit var appCompatTaskInfo: AppCompatTaskInfo
+    @Mock private lateinit var transaction: Transaction
+
+    private lateinit var customCrossActivityBackAnimation: CustomCrossActivityBackAnimation
+    private lateinit var customAnimationLoader: CustomAnimationLoader
+
+    @Before
+    @Throws(Exception::class)
+    fun setUp() {
+        customAnimationLoader = CustomAnimationLoader(transitionAnimation)
+        customCrossActivityBackAnimation =
+            CustomCrossActivityBackAnimation(
+                context,
+                backAnimationBackground,
+                rootTaskDisplayAreaOrganizer,
+                transaction,
+                mock(Choreographer::class.java),
+                customAnimationLoader
+            )
+
+        whenever(transitionAnimation.loadAppTransitionAnimation(eq(PACKAGE_NAME), eq(OPEN_RES_ID)))
+            .thenReturn(mockOpenAnimation)
+        whenever(transitionAnimation.loadAppTransitionAnimation(eq(PACKAGE_NAME), eq(CLOSE_RES_ID)))
+            .thenReturn(mockCloseAnimation)
+        whenever(transaction.setColor(any(), any())).thenReturn(transaction)
+        whenever(transaction.setAlpha(any(), anyFloat())).thenReturn(transaction)
+        whenever(transaction.setCrop(any(), any())).thenReturn(transaction)
+        whenever(transaction.setRelativeLayer(any(), any(), anyInt())).thenReturn(transaction)
+        spy(customCrossActivityBackAnimation)
+    }
+
+    @Test
+    @Throws(InterruptedException::class)
+    fun receiveFinishAfterInvoke() {
+        val finishCalled = startCustomAnimation()
+        try {
+            customCrossActivityBackAnimation.getRunner().callback.onBackInvoked()
+        } catch (r: RemoteException) {
+            Assert.fail("onBackInvoked throw remote exception")
+        }
+        finishCalled.await(1, TimeUnit.SECONDS)
+    }
+
+    @Test
+    @Throws(InterruptedException::class)
+    fun receiveFinishAfterCancel() {
+        val finishCalled = startCustomAnimation()
+        try {
+            customCrossActivityBackAnimation.getRunner().callback.onBackCancelled()
+        } catch (r: RemoteException) {
+            Assert.fail("onBackCancelled throw remote exception")
+        }
+        finishCalled.await(1, TimeUnit.SECONDS)
+    }
+
+    @Test
+    @Throws(InterruptedException::class)
+    fun receiveFinishWithoutAnimationAfterInvoke() {
+        val finishCalled = startCustomAnimation(targets = arrayOf())
+        try {
+            customCrossActivityBackAnimation.getRunner().callback.onBackInvoked()
+        } catch (r: RemoteException) {
+            Assert.fail("onBackInvoked throw remote exception")
+        }
+        finishCalled.await(1, TimeUnit.SECONDS)
+    }
+
+    @Test
+    fun testLoadCustomAnimation() {
+        testLoadCustomAnimation(OPEN_RES_ID, CLOSE_RES_ID, 0)
+    }
+
+    @Test
+    fun testLoadCustomAnimationNoEnter() {
+        testLoadCustomAnimation(0, CLOSE_RES_ID, 0)
+    }
+
+    @Test
+    fun testLoadWindowAnimations() {
+        testLoadCustomAnimation(0, 0, 30)
+    }
+
+    @Test
+    fun testCustomAnimationHigherThanWindowAnimations() {
+        testLoadCustomAnimation(OPEN_RES_ID, CLOSE_RES_ID, 30)
+    }
+
+    private fun testLoadCustomAnimation(enterResId: Int, exitResId: Int, windowAnimations: Int) {
+        val builder =
+            BackNavigationInfo.Builder()
+                .setCustomAnimation(PACKAGE_NAME, enterResId, exitResId, Color.GREEN)
+                .setWindowAnimations(PACKAGE_NAME, windowAnimations)
+        val info = builder.build().customAnimationInfo!!
+        whenever(
+                transitionAnimation.loadAnimationAttr(
+                    eq(PACKAGE_NAME),
+                    eq(windowAnimations),
+                    anyInt(),
+                    anyBoolean()
+                )
+            )
+            .thenReturn(mockCloseAnimation)
+        whenever(transitionAnimation.loadDefaultAnimationAttr(anyInt(), anyBoolean()))
+            .thenReturn(mockOpenAnimation)
+        val result = customAnimationLoader.loadAll(info)!!
+        if (exitResId != 0) {
+            if (enterResId == 0) {
+                verify(transitionAnimation, never())
+                    .loadAppTransitionAnimation(eq(PACKAGE_NAME), eq(enterResId))
+                verify(transitionAnimation).loadDefaultAnimationAttr(anyInt(), anyBoolean())
+            } else {
+                assertEquals(result.enterAnimation, mockOpenAnimation)
+            }
+            assertEquals(result.backgroundColor.toLong(), Color.GREEN.toLong())
+            assertEquals(result.closeAnimation, mockCloseAnimation)
+            verify(transitionAnimation, never())
+                .loadAnimationAttr(eq(PACKAGE_NAME), anyInt(), anyInt(), anyBoolean())
+        } else if (windowAnimations != 0) {
+            verify(transitionAnimation, times(2))
+                .loadAnimationAttr(eq(PACKAGE_NAME), anyInt(), anyInt(), anyBoolean())
+            Assert.assertEquals(result.closeAnimation, mockCloseAnimation)
+        }
+    }
+
+    private fun startCustomAnimation(
+        targets: Array<RemoteAnimationTarget> =
+            arrayOf(createAnimationTarget(false), createAnimationTarget(true))
+    ): CountDownLatch {
+        val backNavigationInfo =
+            BackNavigationInfo.Builder()
+                .setCustomAnimation(PACKAGE_NAME, OPEN_RES_ID, CLOSE_RES_ID, /*backgroundColor*/ 0)
+                .build()
+        customCrossActivityBackAnimation.prepareNextAnimation(
+            backNavigationInfo.customAnimationInfo,
+            0
+        )
+        val finishCalled = CountDownLatch(1)
+        val finishCallback = Runnable { finishCalled.countDown() }
+        customCrossActivityBackAnimation
+            .getRunner()
+            .startAnimation(targets, null, null, finishCallback)
+        customCrossActivityBackAnimation.runner.callback.onBackStarted(backMotionEventFrom(0f, 0f))
+        if (targets.isNotEmpty()) {
+            verify(mockCloseAnimation)
+                .initialize(eq(BOUND_SIZE), eq(BOUND_SIZE), eq(BOUND_SIZE), eq(BOUND_SIZE))
+            verify(mockOpenAnimation)
+                .initialize(eq(BOUND_SIZE), eq(BOUND_SIZE), eq(BOUND_SIZE), eq(BOUND_SIZE))
+        }
+        return finishCalled
+    }
+
+    private fun backMotionEventFrom(touchX: Float, progress: Float) =
+        BackMotionEvent(
+            /* touchX = */ touchX,
+            /* touchY = */ 0f,
+            /* progress = */ progress,
+            /* velocityX = */ 0f,
+            /* velocityY = */ 0f,
+            /* triggerBack = */ false,
+            /* swipeEdge = */ BackEvent.EDGE_LEFT,
+            /* departingAnimationTarget = */ null
+        )
+
+    private fun createAnimationTarget(open: Boolean): RemoteAnimationTarget {
+        val topWindowLeash = SurfaceControl()
+        val taskInfo = RunningTaskInfo()
+        taskInfo.appCompatTaskInfo = appCompatTaskInfo
+        taskInfo.taskDescription = ActivityManager.TaskDescription()
+        return RemoteAnimationTarget(
+            1,
+            if (open) RemoteAnimationTarget.MODE_OPENING else RemoteAnimationTarget.MODE_CLOSING,
+            topWindowLeash,
+            false,
+            Rect(),
+            Rect(),
+            -1,
+            Point(0, 0),
+            Rect(0, 0, BOUND_SIZE, BOUND_SIZE),
+            Rect(),
+            WindowConfiguration(),
+            true,
+            null,
+            null,
+            taskInfo,
+            false,
+            -1
+        )
+    }
+
+    companion object {
+        private const val BOUND_SIZE = 100
+        private const val OPEN_RES_ID = 1000
+        private const val CLOSE_RES_ID = 1001
+        private const val PACKAGE_NAME = "TestPackage"
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
deleted file mode 100644
index 158d640..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.back;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import android.app.WindowConfiguration;
-import android.graphics.Color;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.Choreographer;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.animation.Animation;
-import android.window.BackNavigationInfo;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.ShellTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@SmallTest
-@TestableLooper.RunWithLooper
-@RunWith(AndroidTestingRunner.class)
-public class CustomizeActivityAnimationTest extends ShellTestCase {
-    private static final int BOUND_SIZE = 100;
-    @Mock
-    private BackAnimationBackground mBackAnimationBackground;
-    @Mock
-    private Animation mMockCloseAnimation;
-    @Mock
-    private Animation mMockOpenAnimation;
-
-    private CustomizeActivityAnimation mCustomizeActivityAnimation;
-
-    @Before
-    public void setUp() throws Exception {
-        mCustomizeActivityAnimation = new CustomizeActivityAnimation(mContext,
-                mBackAnimationBackground, mock(SurfaceControl.Transaction.class),
-                mock(Choreographer.class));
-        spyOn(mCustomizeActivityAnimation);
-        spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation);
-    }
-
-    RemoteAnimationTarget createAnimationTarget(boolean open) {
-        SurfaceControl topWindowLeash = new SurfaceControl();
-        return new RemoteAnimationTarget(1,
-                open ? RemoteAnimationTarget.MODE_OPENING : RemoteAnimationTarget.MODE_CLOSING,
-                topWindowLeash, false, new Rect(), new Rect(), -1,
-                new Point(0, 0), new Rect(0, 0, BOUND_SIZE, BOUND_SIZE), new Rect(),
-                new WindowConfiguration(), true, null, null, null, false, -1);
-    }
-
-    @Test
-    public void receiveFinishAfterInvoke() throws InterruptedException {
-        spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader);
-        doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
-                .loadAnimation(any(), eq(false));
-        doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
-                .loadAnimation(any(), eq(true));
-
-        mCustomizeActivityAnimation.prepareNextAnimation(
-                new BackNavigationInfo.CustomAnimationInfo("TestPackage"), 0);
-        final RemoteAnimationTarget close = createAnimationTarget(false);
-        final RemoteAnimationTarget open = createAnimationTarget(true);
-        // start animation with remote animation targets
-        final CountDownLatch finishCalled = new CountDownLatch(1);
-        final Runnable finishCallback = finishCalled::countDown;
-        mCustomizeActivityAnimation
-                .getRunner()
-                .startAnimation(
-                        new RemoteAnimationTarget[] {close, open}, null, null, finishCallback);
-        verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
-                eq(BOUND_SIZE), eq(BOUND_SIZE));
-        verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
-                eq(BOUND_SIZE), eq(BOUND_SIZE));
-
-        try {
-            mCustomizeActivityAnimation.getRunner().getCallback().onBackInvoked();
-        } catch (RemoteException r) {
-            fail("onBackInvoked throw remote exception");
-        }
-        verify(mCustomizeActivityAnimation).onGestureCommitted();
-        finishCalled.await(1, TimeUnit.SECONDS);
-    }
-
-    @Test
-    public void receiveFinishAfterCancel() throws InterruptedException {
-        spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader);
-        doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
-                .loadAnimation(any(), eq(false));
-        doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
-                .loadAnimation(any(), eq(true));
-
-        mCustomizeActivityAnimation.prepareNextAnimation(
-                new BackNavigationInfo.CustomAnimationInfo("TestPackage"), 0);
-        final RemoteAnimationTarget close = createAnimationTarget(false);
-        final RemoteAnimationTarget open = createAnimationTarget(true);
-        // start animation with remote animation targets
-        final CountDownLatch finishCalled = new CountDownLatch(1);
-        final Runnable finishCallback = finishCalled::countDown;
-        mCustomizeActivityAnimation
-                .getRunner()
-                .startAnimation(
-                        new RemoteAnimationTarget[] {close, open}, null, null, finishCallback);
-        verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
-                eq(BOUND_SIZE), eq(BOUND_SIZE));
-        verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
-                eq(BOUND_SIZE), eq(BOUND_SIZE));
-
-        try {
-            mCustomizeActivityAnimation.getRunner().getCallback().onBackCancelled();
-        } catch (RemoteException r) {
-            fail("onBackCancelled throw remote exception");
-        }
-        finishCalled.await(1, TimeUnit.SECONDS);
-    }
-
-    @Test
-    public void receiveFinishWithoutAnimationAfterInvoke() throws InterruptedException {
-        mCustomizeActivityAnimation.prepareNextAnimation(
-                new BackNavigationInfo.CustomAnimationInfo("TestPackage"), 0);
-        // start animation without any remote animation targets
-        final CountDownLatch finishCalled = new CountDownLatch(1);
-        final Runnable finishCallback = finishCalled::countDown;
-        mCustomizeActivityAnimation
-                .getRunner()
-                .startAnimation(new RemoteAnimationTarget[] {}, null, null, finishCallback);
-
-        try {
-            mCustomizeActivityAnimation.getRunner().getCallback().onBackInvoked();
-        } catch (RemoteException r) {
-            fail("onBackInvoked throw remote exception");
-        }
-        verify(mCustomizeActivityAnimation).onGestureCommitted();
-        finishCalled.await(1, TimeUnit.SECONDS);
-    }
-
-    @Test
-    public void testLoadCustomAnimation() {
-        testLoadCustomAnimation(10, 20, 0);
-    }
-
-    @Test
-    public void testLoadCustomAnimationNoEnter() {
-        testLoadCustomAnimation(0, 10, 0);
-    }
-
-    @Test
-    public void testLoadWindowAnimations() {
-        testLoadCustomAnimation(0, 0, 30);
-    }
-
-    @Test
-    public void testCustomAnimationHigherThanWindowAnimations() {
-        testLoadCustomAnimation(10, 20, 30);
-    }
-
-    private void testLoadCustomAnimation(int enterResId, int exitResId, int windowAnimations) {
-        final String testPackage = "TestPackage";
-        BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
-                .setCustomAnimation(testPackage, enterResId, exitResId, Color.GREEN)
-                .setWindowAnimations(testPackage, windowAnimations);
-        final BackNavigationInfo.CustomAnimationInfo info = builder.build()
-                .getCustomAnimationInfo();
-
-        doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
-                        .mTransitionAnimation)
-                .loadAppTransitionAnimation(eq(testPackage), eq(enterResId));
-        doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
-                        .mTransitionAnimation)
-                .loadAppTransitionAnimation(eq(testPackage), eq(exitResId));
-        doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
-                        .mTransitionAnimation)
-                .loadAnimationAttr(eq(testPackage), eq(windowAnimations), anyInt(), anyBoolean());
-        doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
-                        .mTransitionAnimation).loadDefaultAnimationAttr(anyInt(), anyBoolean());
-
-        CustomizeActivityAnimation.AnimationLoadResult result =
-                mCustomizeActivityAnimation.mCustomAnimationLoader.loadAll(info);
-
-        if (exitResId != 0) {
-            if (enterResId == 0) {
-                verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation,
-                        never()).loadAppTransitionAnimation(eq(testPackage), eq(enterResId));
-                verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation)
-                        .loadDefaultAnimationAttr(anyInt(), anyBoolean());
-            } else {
-                assertEquals(result.mEnterAnimation, mMockOpenAnimation);
-            }
-            assertEquals(result.mBackgroundColor, Color.GREEN);
-            assertEquals(result.mCloseAnimation, mMockCloseAnimation);
-            verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation, never())
-                    .loadAnimationAttr(eq(testPackage), anyInt(), anyInt(), anyBoolean());
-        } else if (windowAnimations != 0) {
-            verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation,
-                    times(2)).loadAnimationAttr(eq(testPackage), anyInt(), anyInt(), anyBoolean());
-            assertEquals(result.mCloseAnimation, mMockCloseAnimation);
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 6be411d..0f43377 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -1193,23 +1193,6 @@
     }
 
     @Test
-    public void test_removeOverflowBubble() {
-        sendUpdatedEntryAtTime(mEntryA1, 2000);
-        mBubbleData.setListener(mListener);
-
-        mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE);
-        verifyUpdateReceived();
-        assertOverflowChangedTo(ImmutableList.of(mBubbleA1));
-
-        mBubbleData.removeOverflowBubble(mBubbleA1);
-        verifyUpdateReceived();
-
-        BubbleData.Update update = mUpdateCaptor.getValue();
-        assertThat(update.removedOverflowBubble).isEqualTo(mBubbleA1);
-        assertOverflowChangedTo(ImmutableList.of());
-    }
-
-    @Test
     public void test_getInitialStateForBubbleBar_includesInitialBubblesAndPosition() {
         sendUpdatedEntryAtTime(mEntryA1, 1000);
         sendUpdatedEntryAtTime(mEntryA2, 2000);
@@ -1235,6 +1218,51 @@
         assertExpandedChangedTo(true);
     }
 
+    @Test
+    public void testShowOverflowChanged_hasOverflowBubbles() {
+        assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        mBubbleData.setListener(mListener);
+
+        mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE);
+        verifyUpdateReceived();
+        assertThat(mUpdateCaptor.getValue().showOverflowChanged).isTrue();
+        assertThat(mBubbleData.getOverflowBubbles()).isNotEmpty();
+    }
+
+    @Test
+    public void testShowOverflowChanged_false_hasOverflowBubbles() {
+        assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        sendUpdatedEntryAtTime(mEntryA2, 1000);
+        mBubbleData.setListener(mListener);
+
+        // First overflowed causes change event
+        mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE);
+        verifyUpdateReceived();
+        assertThat(mUpdateCaptor.getValue().showOverflowChanged).isTrue();
+        assertThat(mBubbleData.getOverflowBubbles()).isNotEmpty();
+
+        // Second overflow does not
+        mBubbleData.dismissBubbleWithKey(mEntryA2.getKey(), Bubbles.DISMISS_USER_GESTURE);
+        verifyUpdateReceived();
+        assertThat(mUpdateCaptor.getValue().showOverflowChanged).isFalse();
+    }
+
+    @Test
+    public void testShowOverflowChanged_noOverflowBubbles() {
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE);
+        assertThat(mBubbleData.getOverflowBubbles()).isNotEmpty();
+        mBubbleData.setListener(mListener);
+
+        mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_NOTIF_CANCEL);
+
+        verifyUpdateReceived();
+        assertThat(mUpdateCaptor.getValue().showOverflowChanged).isTrue();
+        assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
+    }
+
     private void verifyUpdateReceived() {
         verify(mListener).applyUpdate(mUpdateCaptor.capture());
         reset(mListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 56d0f8e1..cfe8e07 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -26,6 +26,7 @@
 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.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
@@ -115,27 +116,27 @@
 
     @Test
     public void testUpdateDivideBounds() {
-        mSplitLayout.updateDivideBounds(anyInt());
+        mSplitLayout.updateDividerBounds(anyInt(), anyBoolean());
         verify(mSplitLayoutHandler).onLayoutSizeChanging(any(SplitLayout.class), anyInt(),
-                anyInt());
+                anyInt(), anyBoolean());
     }
 
     @Test
     public void testSetDividePosition() {
-        mSplitLayout.setDividePosition(100, false /* applyLayoutChange */);
-        assertThat(mSplitLayout.getDividePosition()).isEqualTo(100);
+        mSplitLayout.setDividerPosition(100, false /* applyLayoutChange */);
+        assertThat(mSplitLayout.getDividerPosition()).isEqualTo(100);
         verify(mSplitLayoutHandler, never()).onLayoutSizeChanged(any(SplitLayout.class));
 
-        mSplitLayout.setDividePosition(200, true /* applyLayoutChange */);
-        assertThat(mSplitLayout.getDividePosition()).isEqualTo(200);
+        mSplitLayout.setDividerPosition(200, true /* applyLayoutChange */);
+        assertThat(mSplitLayout.getDividerPosition()).isEqualTo(200);
         verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class));
     }
 
     @Test
     public void testSetDivideRatio() {
-        mSplitLayout.setDividePosition(200, false /* applyLayoutChange */);
+        mSplitLayout.setDividerPosition(200, false /* applyLayoutChange */);
         mSplitLayout.setDivideRatio(SNAP_TO_50_50);
-        assertThat(mSplitLayout.getDividePosition()).isEqualTo(
+        assertThat(mSplitLayout.getDividerPosition()).isEqualTo(
                 mSplitLayout.mDividerSnapAlgorithm.getMiddleTarget().position);
     }
 
@@ -152,7 +153,7 @@
         DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
                 SNAP_TO_START_AND_DISMISS);
 
-        mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget);
+        mSplitLayout.snapToTarget(mSplitLayout.getDividerPosition(), snapTarget);
         waitDividerFlingFinished();
         verify(mSplitLayoutHandler).onSnappedToDismiss(eq(false), anyInt());
     }
@@ -164,7 +165,7 @@
         DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
                 SNAP_TO_END_AND_DISMISS);
 
-        mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget);
+        mSplitLayout.snapToTarget(mSplitLayout.getDividerPosition(), snapTarget);
         waitDividerFlingFinished();
         verify(mSplitLayoutHandler).onSnappedToDismiss(eq(true), anyInt());
     }
@@ -188,7 +189,7 @@
     }
 
     private void waitDividerFlingFinished() {
-        verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), anyInt(),
+        verify(mSplitLayout).flingDividerPosition(anyInt(), anyInt(), anyInt(),
                 mRunnableCaptor.capture());
         mRunnableCaptor.getValue().run();
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index afae653..9c00864 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -668,6 +668,18 @@
         Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton());
     }
 
+    @Test
+    public void testLetterboxEduLayout_notCreatedWhenLetterboxEducationIsDisabled() {
+        TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
+        taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = false;
+
+        mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+
+        verify(mController, never()).createLetterboxEduWindowManager(any(), eq(taskInfo),
+                eq(mMockTaskListener));
+    }
+
     private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
             @CameraCompatControlState int cameraCompatControlState) {
         return createTaskInfo(displayId, taskId, hasSizeCompat, cameraCompatControlState,
@@ -694,6 +706,8 @@
         taskInfo.isVisible = isVisible;
         taskInfo.isFocused = isFocused;
         taskInfo.isTopActivityTransparent = isTopActivityTransparent;
+        taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = true;
+        taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed = true;
         return taskInfo;
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index 5209d0e..41a81c1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -22,6 +22,7 @@
 import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
 import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
@@ -98,14 +99,28 @@
 
     private CompatUIWindowManager mWindowManager;
     private TaskInfo mTaskInfo;
+    private DisplayLayout mDisplayLayout;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         doReturn(100).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
         mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
+
+        final DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = TASK_WIDTH;
+        displayInfo.logicalHeight = TASK_HEIGHT;
+        mDisplayLayout = new DisplayLayout(displayInfo,
+                mContext.getResources(), /* hasNavigationBar= */ true, /* hasStatusBar= */ false);
+        final InsetsState insetsState = new InsetsState();
+        insetsState.setDisplayFrame(new Rect(0, 0, TASK_WIDTH, TASK_HEIGHT));
+        final InsetsSource insetsSource = new InsetsSource(
+                InsetsSource.createId(null, 0, navigationBars()), navigationBars());
+        insetsSource.setFrame(0, TASK_HEIGHT - 200, TASK_WIDTH, TASK_HEIGHT);
+        insetsState.addSource(insetsSource);
+        mDisplayLayout.setInsets(mContext.getResources(), insetsState);
         mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
-                mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+                mCallback, mTaskListener, mDisplayLayout, new CompatUIHintsState(),
                 mCompatUIConfiguration, mOnRestartButtonClicked);
 
         spyOn(mWindowManager);
@@ -363,9 +378,9 @@
 
         // Update if the insets change on the existing display layout
         clearInvocations(mWindowManager);
-        InsetsState insetsState = new InsetsState();
+        final InsetsState insetsState = new InsetsState();
         insetsState.setDisplayFrame(new Rect(0, 0, 1000, 2000));
-        InsetsSource insetsSource = new InsetsSource(
+        final InsetsSource insetsSource = new InsetsSource(
                 InsetsSource.createId(null, 0, navigationBars()), navigationBars());
         insetsSource.setFrame(0, 1800, 1000, 2000);
         insetsState.addSource(insetsSource);
@@ -493,16 +508,14 @@
     @Test
     public void testShouldShowSizeCompatRestartButton() {
         mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON);
-
-        doReturn(86).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
+        doReturn(85).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
         mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
-                mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+                mCallback, mTaskListener, mDisplayLayout, new CompatUIHintsState(),
                 mCompatUIConfiguration, mOnRestartButtonClicked);
 
         // Simulate rotation of activity in square display
         TaskInfo taskInfo = createTaskInfo(true, CAMERA_COMPAT_CONTROL_HIDDEN);
-        taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
-        taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 2000;
+        taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = TASK_HEIGHT;
         taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1850;
 
         assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
@@ -512,11 +525,21 @@
         assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
 
         // Simulate folding
-        taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 1000, 2000));
-        assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+        final InsetsState insetsState = new InsetsState();
+        insetsState.setDisplayFrame(new Rect(0, 0, 1000, TASK_HEIGHT));
+        final InsetsSource insetsSource = new InsetsSource(
+                InsetsSource.createId(null, 0, navigationBars()), navigationBars());
+        insetsSource.setFrame(0, TASK_HEIGHT - 200, 1000, TASK_HEIGHT);
+        insetsState.addSource(insetsSource);
+        mDisplayLayout.setInsets(mContext.getResources(), insetsState);
+        mWindowManager.updateDisplayLayout(mDisplayLayout);
+        taskInfo.configuration.smallestScreenWidthDp = LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP - 100;
+        assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
 
-        taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
-        taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 500;
+        // Simulate floating app with 90& area, more than tolerance
+        taskInfo.configuration.smallestScreenWidthDp = LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
+        taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 950;
+        taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1900;
         assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
     }
 
@@ -529,10 +552,10 @@
                 cameraCompatControlState;
         taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK;
         // Letterboxed activity that takes half the screen should show size compat restart button
-        taskInfo.configuration.windowConfiguration.setBounds(
-                new Rect(0, 0, TASK_WIDTH, TASK_HEIGHT));
         taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1000;
         taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
+        // Screen width dp larger than a normal phone.
+        taskInfo.configuration.smallestScreenWidthDp = LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
         return taskInfo;
     }
 }
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 65117f7..2a2483d 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
@@ -18,6 +18,7 @@
 import android.app.ActivityManager
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.content.Context
 import android.os.IBinder
 import android.testing.AndroidTestingRunner
 import android.view.SurfaceControl
@@ -35,10 +36,12 @@
 import android.window.TransitionInfo.Change
 import android.window.WindowContainerToken
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
 import com.android.modules.utils.testing.ExtendedMockitoRule
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.shared.DesktopModeStatus
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.TransitionInfoBuilder
 import com.android.wm.shell.transition.Transitions
@@ -58,6 +61,11 @@
 import org.mockito.kotlin.same
 import org.mockito.kotlin.times
 
+/**
+ * Test class for {@link DesktopModeLoggerTransitionObserver}
+ *
+ * Usage: atest WMShellUnitTests:DesktopModeLoggerTransitionObserverTest
+ */
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class DesktopModeLoggerTransitionObserverTest {
@@ -74,6 +82,8 @@
     private lateinit var mockShellInit: ShellInit
     @Mock
     private lateinit var transitions: Transitions
+    @Mock
+    private lateinit var context: Context
 
     private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver
     private lateinit var shellInit: ShellInit
@@ -81,12 +91,12 @@
 
     @Before
     fun setup() {
-        Mockito.`when`(DesktopModeStatus.isEnabled()).thenReturn(true)
+        doReturn(true).`when`{ DesktopModeStatus.canEnterDesktopMode(any()) }
         shellInit = Mockito.spy(ShellInit(testExecutor))
         desktopModeEventLogger = mock(DesktopModeEventLogger::class.java)
 
         transitionObserver = DesktopModeLoggerTransitionObserver(
-            mockShellInit, transitions, desktopModeEventLogger)
+            context, mockShellInit, transitions, desktopModeEventLogger)
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             val initRunnableCaptor = ArgumentCaptor.forClass(
                 Runnable::class.java)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index dca7be1..8f59f30 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -182,18 +182,6 @@
     }
 
     @Test
-    fun addListener_notifiesStashed() {
-        repo.setStashed(DEFAULT_DISPLAY, true)
-        val listener = TestVisibilityListener()
-        val executor = TestShellExecutor()
-        repo.addVisibleTasksListener(listener, executor)
-        executor.flushAll()
-
-        assertThat(listener.stashedOnDefaultDisplay).isTrue()
-        assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1)
-    }
-
-    @Test
     fun addListener_tasksOnDifferentDisplay_doesNotNotify() {
         repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true)
         val listener = TestVisibilityListener()
@@ -400,65 +388,6 @@
     }
 
     @Test
-    fun setStashed_stateIsUpdatedForTheDisplay() {
-        repo.setStashed(DEFAULT_DISPLAY, true)
-        assertThat(repo.isStashed(DEFAULT_DISPLAY)).isTrue()
-        assertThat(repo.isStashed(SECOND_DISPLAY)).isFalse()
-
-        repo.setStashed(DEFAULT_DISPLAY, false)
-        assertThat(repo.isStashed(DEFAULT_DISPLAY)).isFalse()
-    }
-
-    @Test
-    fun setStashed_notifyListener() {
-        val listener = TestVisibilityListener()
-        val executor = TestShellExecutor()
-        repo.addVisibleTasksListener(listener, executor)
-        repo.setStashed(DEFAULT_DISPLAY, true)
-        executor.flushAll()
-        assertThat(listener.stashedOnDefaultDisplay).isTrue()
-        assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1)
-
-        repo.setStashed(DEFAULT_DISPLAY, false)
-        executor.flushAll()
-        assertThat(listener.stashedOnDefaultDisplay).isFalse()
-        assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(2)
-    }
-
-    @Test
-    fun setStashed_secondCallDoesNotNotify() {
-        val listener = TestVisibilityListener()
-        val executor = TestShellExecutor()
-        repo.addVisibleTasksListener(listener, executor)
-        repo.setStashed(DEFAULT_DISPLAY, true)
-        repo.setStashed(DEFAULT_DISPLAY, true)
-        executor.flushAll()
-        assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1)
-    }
-
-    @Test
-    fun setStashed_tracksPerDisplay() {
-        val listener = TestVisibilityListener()
-        val executor = TestShellExecutor()
-        repo.addVisibleTasksListener(listener, executor)
-
-        repo.setStashed(DEFAULT_DISPLAY, true)
-        executor.flushAll()
-        assertThat(listener.stashedOnDefaultDisplay).isTrue()
-        assertThat(listener.stashedOnSecondaryDisplay).isFalse()
-
-        repo.setStashed(SECOND_DISPLAY, true)
-        executor.flushAll()
-        assertThat(listener.stashedOnDefaultDisplay).isTrue()
-        assertThat(listener.stashedOnSecondaryDisplay).isTrue()
-
-        repo.setStashed(DEFAULT_DISPLAY, false)
-        executor.flushAll()
-        assertThat(listener.stashedOnDefaultDisplay).isFalse()
-        assertThat(listener.stashedOnSecondaryDisplay).isTrue()
-    }
-
-    @Test
     fun removeFreeformTask_removesTaskBoundsBeforeMaximize() {
         val taskId = 1
         repo.saveBoundsBeforeMaximize(taskId, Rect(0, 0, 200, 200))
@@ -598,12 +527,6 @@
         var visibleChangesOnDefaultDisplay = 0
         var visibleChangesOnSecondaryDisplay = 0
 
-        var stashedOnDefaultDisplay = false
-        var stashedOnSecondaryDisplay = false
-
-        var stashedChangesOnDefaultDisplay = 0
-        var stashedChangesOnSecondaryDisplay = 0
-
         override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
             when (displayId) {
                 DEFAULT_DISPLAY -> {
@@ -617,20 +540,6 @@
                 else -> fail("Visible task listener received unexpected display id: $displayId")
             }
         }
-
-        override fun onStashedChanged(displayId: Int, stashed: Boolean) {
-            when (displayId) {
-                DEFAULT_DISPLAY -> {
-                    stashedOnDefaultDisplay = stashed
-                    stashedChangesOnDefaultDisplay++
-                }
-                SECOND_DISPLAY -> {
-                    stashedOnSecondaryDisplay = stashed
-                    stashedChangesOnDefaultDisplay++
-                }
-                else -> fail("Visible task listener received unexpected display id: $displayId")
-            }
-        }
     }
 
     companion object {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt
new file mode 100644
index 0000000..285e5b6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.desktopmode
+
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.InstanceIdSequence
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.Companion.DesktopUiEventEnum.DESKTOP_WINDOW_EDGE_DRAG_RESIZE
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Test class for [DesktopModeUiEventLogger]
+ *
+ * Usage: atest WMShellUnitTests:DesktopModeUiEventLoggerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopModeUiEventLoggerTest : ShellTestCase() {
+    private lateinit var uiEventLoggerFake: UiEventLoggerFake
+    private lateinit var logger: DesktopModeUiEventLogger
+    private val instanceIdSequence = InstanceIdSequence(10)
+
+
+    @Before
+    fun setUp() {
+        uiEventLoggerFake = UiEventLoggerFake()
+        logger = DesktopModeUiEventLogger(uiEventLoggerFake, instanceIdSequence)
+    }
+
+    @Test
+    fun log_invalidUid_eventNotLogged() {
+        logger.log(-1, PACKAGE_NAME, DESKTOP_WINDOW_EDGE_DRAG_RESIZE)
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0)
+    }
+
+    @Test
+    fun log_emptyPackageName_eventNotLogged() {
+        logger.log(UID, "", DESKTOP_WINDOW_EDGE_DRAG_RESIZE)
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0)
+    }
+
+    @Test
+    fun log_eventLogged() {
+        val event =
+            DESKTOP_WINDOW_EDGE_DRAG_RESIZE
+        logger.log(UID, PACKAGE_NAME, event)
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+        assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(event.id)
+        assertThat(uiEventLoggerFake[0].instanceId).isNull()
+        assertThat(uiEventLoggerFake[0].uid).isEqualTo(UID)
+        assertThat(uiEventLoggerFake[0].packageName).isEqualTo(PACKAGE_NAME)
+    }
+
+    @Test
+    fun getNewInstanceId() {
+        val first = logger.getNewInstanceId()
+        assertThat(first).isNotEqualTo(logger.getNewInstanceId())
+    }
+
+    @Test
+    fun logWithInstanceId_invalidUid_eventNotLogged() {
+        logger.logWithInstanceId(INSTANCE_ID, -1, PACKAGE_NAME, DESKTOP_WINDOW_EDGE_DRAG_RESIZE)
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0)
+    }
+
+    @Test
+    fun logWithInstanceId_emptyPackageName_eventNotLogged() {
+        logger.logWithInstanceId(INSTANCE_ID, UID, "", DESKTOP_WINDOW_EDGE_DRAG_RESIZE)
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0)
+    }
+
+    @Test
+    fun logWithInstanceId_eventLogged() {
+        val event =
+            DESKTOP_WINDOW_EDGE_DRAG_RESIZE
+        logger.logWithInstanceId(INSTANCE_ID, UID, PACKAGE_NAME, event)
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+        assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(event.id)
+        assertThat(uiEventLoggerFake[0].instanceId).isEqualTo(INSTANCE_ID)
+        assertThat(uiEventLoggerFake[0].uid).isEqualTo(UID)
+        assertThat(uiEventLoggerFake[0].packageName).isEqualTo(PACKAGE_NAME)
+    }
+
+
+    companion object {
+        private val INSTANCE_ID = InstanceId.fakeInstanceId(0)
+        private const val UID = 10
+        private const val PACKAGE_NAME = "com.foo"
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 27cd3bf..14f57bd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -24,6 +24,12 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
 import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
 import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.content.res.Configuration.ORIENTATION_PORTRAIT
 import android.graphics.Point
 import android.graphics.PointF
 import android.graphics.Rect
@@ -73,6 +79,7 @@
 import com.android.wm.shell.draganddrop.DragAndDropController
 import com.android.wm.shell.recents.RecentsTransitionHandler
 import com.android.wm.shell.recents.RecentsTransitionStateListener
+import com.android.wm.shell.shared.DesktopModeStatus
 import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
@@ -101,7 +108,9 @@
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
+import org.mockito.kotlin.anyOrNull
 import org.mockito.kotlin.atLeastOnce
 import org.mockito.kotlin.capture
 import org.mockito.quality.Strictness
@@ -141,6 +150,7 @@
     @Mock lateinit var dragAndDropController: DragAndDropController
     @Mock lateinit var multiInstanceHelper: MultiInstanceHelper
     @Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver
+    @Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator
 
     private lateinit var mockitoSession: StaticMockitoSession
     private lateinit var controller: DesktopTasksController
@@ -154,13 +164,23 @@
     // Mock running tasks are registered here so we can get the list from mock shell task organizer
     private val runningTasks = mutableListOf<RunningTaskInfo>()
 
+    private val DISPLAY_DIMENSION_SHORT = 1600
+    private val DISPLAY_DIMENSION_LONG = 2560
+    private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 200, 2240, 1400)
+    private val DEFAULT_PORTRAIT_BOUNDS = Rect(200, 320, 1400, 2240)
+    private val RESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 680, 1575, 1880)
+    private val RESIZABLE_PORTRAIT_BOUNDS = Rect(680, 200, 1880, 1400)
+    private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 699, 1575, 1861)
+    private val UNRESIZABLE_PORTRAIT_BOUNDS = Rect(830, 200, 1730, 1400)
+
     @Before
     fun setUp() {
         mockitoSession = mockitoSession().strictness(Strictness.LENIENT)
             .spyStatic(DesktopModeStatus::class.java).startMocking()
         whenever(DesktopModeStatus.isEnabled()).thenReturn(true)
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
 
-        shellInit = Mockito.spy(ShellInit(testExecutor))
+        shellInit = spy(ShellInit(testExecutor))
         desktopModeTaskRepository = DesktopModeTaskRepository()
         desktopTasksLimiter =
                 DesktopTasksLimiter(transitions, desktopModeTaskRepository, shellTaskOrganizer)
@@ -463,6 +483,135 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun moveToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val task = setUpFullscreenTask()
+        setUpLandscapeDisplay()
+
+        controller.moveToDesktop(task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun moveToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
+        setUpLandscapeDisplay()
+
+        controller.moveToDesktop(task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun moveToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+            shouldLetterbox = true)
+        setUpLandscapeDisplay()
+
+        controller.moveToDesktop(task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun moveToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val task = setUpFullscreenTask(isResizable = false,
+            screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
+        setUpLandscapeDisplay()
+
+        controller.moveToDesktop(task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun moveToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val task = setUpFullscreenTask(isResizable = false,
+            screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true)
+        setUpLandscapeDisplay()
+
+        controller.moveToDesktop(task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun moveToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT)
+        setUpPortraitDisplay()
+
+        controller.moveToDesktop(task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun moveToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT,
+            screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
+        setUpPortraitDisplay()
+
+        controller.moveToDesktop(task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun moveToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT,
+            screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true)
+        setUpPortraitDisplay()
+
+        controller.moveToDesktop(task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun moveToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val task = setUpFullscreenTask(isResizable = false,
+            deviceOrientation = ORIENTATION_PORTRAIT,
+            screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
+        setUpPortraitDisplay()
+
+        controller.moveToDesktop(task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun moveToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val task = setUpFullscreenTask(isResizable = false,
+            deviceOrientation = ORIENTATION_PORTRAIT,
+            screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true)
+        setUpPortraitDisplay()
+
+        controller.moveToDesktop(task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
+    }
+
+    @Test
     fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() {
         val task = setUpFullscreenTask()
         val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
@@ -896,29 +1045,6 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
-    fun handleRequest_fullscreenTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
-        assumeTrue(ENABLE_SHELL_TRANSITIONS)
-        whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
-
-        val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY)
-        markTaskHidden(stashedFreeformTask)
-
-        val fullscreenTask = createFullscreenTask(DEFAULT_DISPLAY)
-
-        controller.stashDesktopApps(DEFAULT_DISPLAY)
-
-        val result = controller.handleRequest(Binder(), createTransition(fullscreenTask))
-        assertThat(result).isNotNull()
-        result!!.assertReorderSequence(stashedFreeformTask, fullscreenTask)
-        assertThat(result.changes[fullscreenTask.token.asBinder()]?.windowingMode)
-                .isEqualTo(WINDOWING_MODE_FREEFORM)
-
-        // Stashed state should be cleared
-        assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse()
-    }
-
-    @Test
     fun handleRequest_freeformTask_freeformVisible_returnNull() {
         assumeTrue(ENABLE_SHELL_TRANSITIONS)
 
@@ -987,27 +1113,6 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
-    fun handleRequest_freeformTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
-        assumeTrue(ENABLE_SHELL_TRANSITIONS)
-        whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
-
-        val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY)
-        markTaskHidden(stashedFreeformTask)
-
-        val freeformTask = createFreeformTask(DEFAULT_DISPLAY)
-
-        controller.stashDesktopApps(DEFAULT_DISPLAY)
-
-        val result = controller.handleRequest(Binder(), createTransition(freeformTask))
-        assertThat(result).isNotNull()
-        result?.assertReorderSequence(stashedFreeformTask, freeformTask)
-
-        // Stashed state should be cleared
-        assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse()
-    }
-
-    @Test
     fun handleRequest_notOpenOrToFrontTransition_returnNull() {
         assumeTrue(ENABLE_SHELL_TRANSITIONS)
 
@@ -1123,29 +1228,6 @@
     }
 
     @Test
-    fun stashDesktopApps_stateUpdates() {
-        whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
-
-        controller.stashDesktopApps(DEFAULT_DISPLAY)
-
-        assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isTrue()
-        assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isFalse()
-    }
-
-    @Test
-    fun hideStashedDesktopApps_stateUpdates() {
-        whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
-
-        desktopModeTaskRepository.setStashed(DEFAULT_DISPLAY, true)
-        desktopModeTaskRepository.setStashed(SECOND_DISPLAY, true)
-        controller.hideStashedDesktopApps(DEFAULT_DISPLAY)
-
-        assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse()
-        // Check that second display is not affected
-        assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isTrue()
-    }
-
-    @Test
     fun desktopTasksVisibilityChange_visible_setLaunchAdjacentDisabled() {
         val task = setUpFreeformTask()
         clearInvocations(launchAdjacentController)
@@ -1226,6 +1308,185 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val spyController = spy(controller)
+        whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+        whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+                .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+        val task = setUpFullscreenTask()
+        setUpLandscapeDisplay()
+
+        spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+        val wct = getLatestDragToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun dragToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val spyController = spy(controller)
+        whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+        whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+                .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+        val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
+        setUpLandscapeDisplay()
+
+        spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+        val wct = getLatestDragToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun dragToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val spyController = spy(controller)
+        whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+        whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+                .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+        val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+            shouldLetterbox = true)
+        setUpLandscapeDisplay()
+
+        spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+        val wct = getLatestDragToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun dragToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val spyController = spy(controller)
+        whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+        whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+                .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+        val task = setUpFullscreenTask(isResizable = false,
+            screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
+        setUpLandscapeDisplay()
+
+        spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+        val wct = getLatestDragToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun dragToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val spyController = spy(controller)
+        whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+        whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+                .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+        val task = setUpFullscreenTask(isResizable = false,
+            screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true)
+        setUpLandscapeDisplay()
+
+        spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+        val wct = getLatestDragToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun dragToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val spyController = spy(controller)
+        whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+        whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+                .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+        val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT)
+        setUpPortraitDisplay()
+
+        spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+        val wct = getLatestDragToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun dragToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val spyController = spy(controller)
+        whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+        whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+                .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+        val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT,
+            screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
+        setUpPortraitDisplay()
+
+        spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+        val wct = getLatestDragToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun dragToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val spyController = spy(controller)
+        whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+        whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+                .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+        val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT,
+            screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true)
+        setUpPortraitDisplay()
+
+        spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+        val wct = getLatestDragToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun dragToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val spyController = spy(controller)
+        whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+        whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+                .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+        val task = setUpFullscreenTask(isResizable = false,
+            deviceOrientation = ORIENTATION_PORTRAIT,
+            screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
+        setUpPortraitDisplay()
+
+        spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+        val wct = getLatestDragToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    fun dragToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() {
+        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val spyController = spy(controller)
+        whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+        whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+                .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+        val task = setUpFullscreenTask(isResizable = false,
+            deviceOrientation = ORIENTATION_PORTRAIT,
+            screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true)
+        setUpPortraitDisplay()
+
+        spyController.onDragPositioningEndThroughStatusBar(PointF(200f, 200f), task)
+        val wct = getLatestDragToDesktopWct()
+        assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
+    }
+
+    @Test
     fun onDesktopDragMove_endsOutsideValidDragArea_snapsToValidBounds() {
         val task = setUpFreeformTask()
         val mockSurface = mock(SurfaceControl::class.java)
@@ -1277,8 +1538,7 @@
         controller.toggleDesktopTaskSize(task)
         // Assert bounds set to stable bounds
         val wct = getLatestToggleResizeDesktopTaskWct()
-        assertThat(wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds)
-                .isEqualTo(STABLE_BOUNDS)
+        assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS)
     }
 
     @Test
@@ -1305,8 +1565,7 @@
 
         // Assert bounds set to last bounds before maximize
         val wct = getLatestToggleResizeDesktopTaskWct()
-        assertThat(wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds)
-                .isEqualTo(boundsBeforeMaximize)
+        assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
     }
 
     @Test
@@ -1347,18 +1606,67 @@
         return task
     }
 
-    private fun setUpFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+    private fun setUpFullscreenTask(
+        displayId: Int = DEFAULT_DISPLAY,
+        isResizable: Boolean = true,
+        windowingMode: Int = WINDOWING_MODE_FULLSCREEN,
+        deviceOrientation: Int = ORIENTATION_LANDSCAPE,
+        screenOrientation: Int = SCREEN_ORIENTATION_UNSPECIFIED,
+        shouldLetterbox: Boolean = false
+    ): RunningTaskInfo {
         val task = createFullscreenTask(displayId)
-        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        val activityInfo = ActivityInfo()
+        activityInfo.screenOrientation = screenOrientation
+        with(task) {
+            topActivityInfo = activityInfo
+            isResizeable = isResizable
+            configuration.orientation = deviceOrientation
+            configuration.windowConfiguration.windowingMode = windowingMode
+
+            if (shouldLetterbox) {
+                if (deviceOrientation == ORIENTATION_LANDSCAPE &&
+                    screenOrientation == SCREEN_ORIENTATION_PORTRAIT) {
+                    // Letterbox to portrait size
+                    appCompatTaskInfo.topActivityBoundsLetterboxed = true
+                    appCompatTaskInfo.topActivityLetterboxWidth = 1200
+                    appCompatTaskInfo.topActivityLetterboxHeight = 1600
+                } else if (deviceOrientation == ORIENTATION_PORTRAIT &&
+                    screenOrientation == SCREEN_ORIENTATION_LANDSCAPE) {
+                    // Letterbox to landscape size
+                    appCompatTaskInfo.topActivityBoundsLetterboxed = true
+                    appCompatTaskInfo.topActivityLetterboxWidth = 1600
+                    appCompatTaskInfo.topActivityLetterboxHeight = 1200
+                }
+            } else {
+                appCompatTaskInfo.topActivityBoundsLetterboxed = false
+            }
+
+            if (deviceOrientation == ORIENTATION_LANDSCAPE) {
+                configuration.windowConfiguration.appBounds = Rect(0, 0,
+                    DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT)
+            } else {
+                configuration.windowConfiguration.appBounds = Rect(0, 0,
+                    DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG)
+            }
+        }
         whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
         whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
         runningTasks.add(task)
         return task
     }
 
+    private fun setUpLandscapeDisplay() {
+        whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_LONG)
+        whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_SHORT)
+    }
+
+    private fun setUpPortraitDisplay() {
+        whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_SHORT)
+        whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_LONG)
+    }
+
     private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
         val task = createSplitScreenTask(displayId)
-        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
         whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
         whenever(splitScreenController.isTaskInSplitScreen(task.taskId)).thenReturn(true)
         whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
@@ -1421,6 +1729,17 @@
         return arg.value
     }
 
+    private fun getLatestDragToDesktopWct(): WindowContainerTransaction {
+        val arg: ArgumentCaptor<WindowContainerTransaction> =
+            ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        if (ENABLE_SHELL_TRANSITIONS) {
+            verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(capture(arg))
+        } else {
+            verify(shellTaskOrganizer).applyTransaction(capture(arg))
+        }
+        return arg.value
+    }
+
     private fun getLatestExitDesktopWct(): WindowContainerTransaction {
         val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
         if (ENABLE_SHELL_TRANSITIONS) {
@@ -1432,6 +1751,10 @@
         return arg.value
     }
 
+    private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? =
+        wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds
+
+
     private fun verifyWCTNotExecuted() {
         if (ENABLE_SHELL_TRANSITIONS) {
             verify(transitions, never()).startTransition(anyInt(), any(), isNull())
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 38ea034..3c488ca 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
@@ -27,10 +27,12 @@
 import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
 import androidx.test.filters.SmallTest
 import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
+import com.android.wm.shell.shared.DesktopModeStatus
 import com.android.wm.shell.transition.TransitionInfoBuilder
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.util.StubTransaction
@@ -41,6 +43,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.any
 import org.mockito.Mockito.`when`
 import org.mockito.quality.Strictness
 
@@ -69,7 +72,7 @@
     fun setUp() {
         mockitoSession = ExtendedMockito.mockitoSession().strictness(Strictness.LENIENT)
                 .spyStatic(DesktopModeStatus::class.java).startMocking()
-        `when`(DesktopModeStatus.isEnabled()).thenReturn(true)
+        doReturn(true).`when`{ DesktopModeStatus.canEnterDesktopMode(any()) }
 
         desktopTaskRepo = DesktopModeTaskRepository()
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
index 71eea4b..cd68c69 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
@@ -19,11 +19,12 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
 
@@ -34,8 +35,8 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.shared.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
@@ -72,8 +73,10 @@
     public void setup() {
         mMockitoSession = mockitoSession().initMocks(this)
                 .strictness(Strictness.LENIENT).mockStatic(DesktopModeStatus.class).startMocking();
-        when(DesktopModeStatus.isEnabled()).thenReturn(true);
+        doReturn(true).when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
+
         mFreeformTaskListener = new FreeformTaskListener(
+                mContext,
                 mShellInit,
                 mTaskOrganizer,
                 Optional.of(mDesktopModeTaskRepository),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 5cf9be4..884cb6e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -59,6 +59,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
 import com.android.window.flags.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
@@ -66,8 +67,8 @@
 import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.shared.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
@@ -75,11 +76,13 @@
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
 import com.android.wm.shell.util.SplitBounds;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.quality.Strictness;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -88,7 +91,9 @@
 import java.util.function.Consumer;
 
 /**
- * Tests for {@link RecentTasksController}.
+ * Tests for {@link RecentTasksController}
+ *
+ * Usage: atest WMShellUnitTests:RecentTasksControllerTest
  */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -118,9 +123,15 @@
     private ShellInit mShellInit;
     private ShellController mShellController;
     private TestShellExecutor mMainExecutor;
+    private static StaticMockitoSession sMockitoSession;
 
     @Before
     public void setUp() {
+        sMockitoSession = mockitoSession().initMocks(this).strictness(Strictness.LENIENT)
+                .mockStatic(DesktopModeStatus.class).startMocking();
+        ExtendedMockito.doReturn(true)
+                .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
+
         mMainExecutor = new TestShellExecutor();
         when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
         mShellInit = spy(new ShellInit(mMainExecutor));
@@ -136,6 +147,11 @@
         mShellInit.init();
     }
 
+    @After
+    public void tearDown() {
+        sMockitoSession.finishMocking();
+    }
+
     @Test
     public void instantiateController_addInitCallback() {
         verify(mShellInit, times(1)).addInitCallback(any(), isA(RecentTasksController.class));
@@ -275,10 +291,6 @@
 
     @Test
     public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_groupFreeformTasks() {
-        StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
-                DesktopModeStatus.class).startMocking();
-        when(DesktopModeStatus.isEnabled()).thenReturn(true);
-
         ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
         ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
         ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
@@ -309,16 +321,10 @@
         // Check single entries
         assertEquals(t2, singleGroup1.getTaskInfo1());
         assertEquals(t4, singleGroup2.getTaskInfo1());
-
-        mockitoSession.finishMocking();
     }
 
     @Test
     public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_freeformTaskOrder() {
-        StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
-                DesktopModeStatus.class).startMocking();
-        when(DesktopModeStatus.isEnabled()).thenReturn(true);
-
         ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
         ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
         ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
@@ -357,15 +363,12 @@
 
         // Check single entry
         assertEquals(t4, singleGroup.getTaskInfo1());
-
-        mockitoSession.finishMocking();
     }
 
     @Test
     public void testGetRecentTasks_hasActiveDesktopTasks_proto2Disabled_doNotGroupFreeformTasks() {
-        StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
-                DesktopModeStatus.class).startMocking();
-        when(DesktopModeStatus.isEnabled()).thenReturn(false);
+        ExtendedMockito.doReturn(false)
+                .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
 
         ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
         ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
@@ -390,16 +393,10 @@
         assertEquals(t2, recentTasks.get(1).getTaskInfo1());
         assertEquals(t3, recentTasks.get(2).getTaskInfo1());
         assertEquals(t4, recentTasks.get(3).getTaskInfo1());
-
-        mockitoSession.finishMocking();
     }
 
     @Test
     public void testGetRecentTasks_proto2Enabled_ignoresMinimizedFreeformTasks() {
-        StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
-                DesktopModeStatus.class).startMocking();
-        when(DesktopModeStatus.isEnabled()).thenReturn(true);
-
         ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
         ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
         ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
@@ -435,8 +432,6 @@
         // Check single entries
         assertEquals(t2, singleGroup1.getTaskInfo1());
         assertEquals(t4, singleGroup2.getTaskInfo1());
-
-        mockitoSession.finishMocking();
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index befc702..34b2eeb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -39,10 +39,13 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -63,6 +66,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
@@ -105,6 +109,8 @@
     @Mock private ShellExecutor mMainExecutor;
     @Mock private LaunchAdjacentController mLaunchAdjacentController;
     @Mock private DefaultMixedHandler mMixedHandler;
+    @Mock private SplitScreen.SplitInvocationListener mInvocationListener;
+    private final TestShellExecutor mTestShellExecutor = new TestShellExecutor();
     private SplitLayout mSplitLayout;
     private MainStage mMainStage;
     private SideStage mSideStage;
@@ -147,6 +153,7 @@
                 .setParentTaskId(mSideStage.mRootTaskInfo.taskId).build();
         doReturn(mock(SplitDecorManager.class)).when(mMainStage).getSplitDecorManager();
         doReturn(mock(SplitDecorManager.class)).when(mSideStage).getSplitDecorManager();
+        mStageCoordinator.registerSplitAnimationListener(mInvocationListener, mTestShellExecutor);
     }
 
     @Test
@@ -452,6 +459,15 @@
         mMainStage.activate(new WindowContainerTransaction(), true /* includingTopTask */);
     }
 
+    @Test
+    @UiThreadTest
+    public void testSplitInvocationCallback() {
+        enterSplit();
+        mTestShellExecutor.flushAll();
+        verify(mInvocationListener, times(1))
+                .onSplitAnimationInvoked(eq(true));
+    }
+
     private boolean containsSplitEnter(@NonNull WindowContainerTransaction wct) {
         for (int i = 0; i < wct.getHierarchyOps().size(); ++i) {
             WindowContainerTransaction.HierarchyOp op = wct.getHierarchyOps().get(i);
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 7d19f3c..282495d 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
@@ -60,8 +60,8 @@
 import com.android.wm.shell.common.DisplayLayout
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.desktopmode.DesktopModeStatus
 import com.android.wm.shell.desktopmode.DesktopTasksController
+import com.android.wm.shell.shared.DesktopModeStatus
 import com.android.wm.shell.sysui.KeyguardChangeListener
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
@@ -121,6 +121,7 @@
     @Mock private lateinit var mockShellController: ShellController
     @Mock private lateinit var mockShellExecutor: ShellExecutor
     @Mock private lateinit var mockRootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+    @Mock private lateinit var mockResizeHandleSizeRepository: ResizeHandleSizeRepository
     @Mock private lateinit var mockShellCommandHandler: ShellCommandHandler
     @Mock private lateinit var mockWindowManager: IWindowManager
 
@@ -156,7 +157,8 @@
                 mockInputMonitorFactory,
                 transactionFactory,
                 mockRootTaskDisplayAreaOrganizer,
-            windowDecorByTaskIdSpy
+                windowDecorByTaskIdSpy,
+                mockResizeHandleSizeRepository
         )
 
         whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -197,7 +199,8 @@
                 mockMainHandler,
                 mockMainChoreographer,
                 mockSyncQueue,
-                mockRootTaskDisplayAreaOrganizer
+                mockRootTaskDisplayAreaOrganizer,
+                mockResizeHandleSizeRepository
         )
         verify(decoration).close()
     }
@@ -221,7 +224,8 @@
                 mockMainHandler,
                 mockMainChoreographer,
                 mockSyncQueue,
-                mockRootTaskDisplayAreaOrganizer
+                mockRootTaskDisplayAreaOrganizer,
+                mockResizeHandleSizeRepository
         )
 
         task.setWindowingMode(WINDOWING_MODE_FREEFORM)
@@ -236,7 +240,8 @@
                 mockMainHandler,
                 mockMainChoreographer,
                 mockSyncQueue,
-                mockRootTaskDisplayAreaOrganizer
+                mockRootTaskDisplayAreaOrganizer,
+                mockResizeHandleSizeRepository
         )
     }
 
@@ -296,7 +301,7 @@
         onTaskChanging(task)
 
         verify(mockDesktopModeWindowDecorFactory, never())
-                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
     }
 
     @Test
@@ -309,7 +314,7 @@
         onTaskOpening(task)
 
         verify(mockDesktopModeWindowDecorFactory, never())
-                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
     }
 
     @Test
@@ -406,7 +411,7 @@
 
             onTaskOpening(task)
             verify(mockDesktopModeWindowDecorFactory, never())
-                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
         } finally {
             mockitoSession.finishMocking()
         }
@@ -430,7 +435,7 @@
 
             onTaskOpening(task)
             verify(mockDesktopModeWindowDecorFactory)
-                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
         } finally {
             mockitoSession.finishMocking()
         }
@@ -453,7 +458,7 @@
 
             onTaskOpening(task)
             verify(mockDesktopModeWindowDecorFactory)
-                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
         } finally {
             mockitoSession.finishMocking()
         }
@@ -497,7 +502,7 @@
         val decoration = mock(DesktopModeWindowDecoration::class.java)
         whenever(
             mockDesktopModeWindowDecorFactory.create(
-                any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+                any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
         ).thenReturn(decoration)
         decoration.mTaskInfo = task
         whenever(decoration.isFocused).thenReturn(task.isFocused)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 608f74b..cff9313 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -39,6 +40,9 @@
 import android.content.res.TypedArray;
 import android.os.Handler;
 import android.os.SystemProperties;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
 import android.view.Choreographer;
@@ -51,6 +55,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.window.flags.Flags;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
@@ -61,6 +66,7 @@
 
 import org.junit.Before;
 import org.junit.BeforeClass;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -83,6 +89,8 @@
     private static final String USE_ROUNDED_CORNERS_SYSPROP_KEY =
             "persist.wm.debug.desktop_use_rounded_corners";
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
     @Mock
     private DisplayController mMockDisplayController;
     @Mock
@@ -107,6 +115,8 @@
     private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory;
     @Mock
     private TypedArray mMockRoundedCornersRadiusArray;
+    @Mock
+    private ResizeHandleSizeRepository mMockResizeHandleSizeRepository;
 
     private final Configuration mConfiguration = new Configuration();
 
@@ -175,6 +185,48 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY)
+    public void updateRelayoutParams_appHeader_usesTaskDensity() {
+        final int systemDensity = mTestableContext.getOrCreateTestableResources().getResources()
+                .getConfiguration().densityDpi;
+        final int customTaskDensity = systemDensity + 300;
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        taskInfo.configuration.densityDpi = customTaskDensity;
+        final RelayoutParams relayoutParams = new RelayoutParams();
+
+        DesktopModeWindowDecoration.updateRelayoutParams(
+                relayoutParams,
+                mTestableContext,
+                taskInfo,
+                /* applyStartTransactionOnDraw= */ true,
+                /* shouldSetTaskPositionAndCrop */ false);
+
+        assertThat(relayoutParams.mWindowDecorConfig.densityDpi).isEqualTo(customTaskDensity);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY)
+    public void updateRelayoutParams_appHeader_usesSystemDensity() {
+        final int systemDensity = mTestableContext.getOrCreateTestableResources().getResources()
+                .getConfiguration().densityDpi;
+        final int customTaskDensity = systemDensity + 300;
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        taskInfo.configuration.densityDpi = customTaskDensity;
+        final RelayoutParams relayoutParams = new RelayoutParams();
+
+        DesktopModeWindowDecoration.updateRelayoutParams(
+                relayoutParams,
+                mTestableContext,
+                taskInfo,
+                /* applyStartTransactionOnDraw= */ true,
+                /* shouldSetTaskPositionAndCrop */ false);
+
+        assertThat(relayoutParams.mWindowDecorConfig.densityDpi).isEqualTo(systemDensity);
+    }
+
+    @Test
     public void updateRelayoutParams_freeformAndTransparentAppearance_allowsInputFallthrough() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -296,8 +348,8 @@
         return new DesktopModeWindowDecoration(mContext, mMockDisplayController,
                 mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mConfiguration,
                 mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
-                SurfaceControl.Builder::new, mMockTransactionSupplier,
-                WindowContainerTransaction::new, SurfaceControl::new,
+                mMockResizeHandleSizeRepository, SurfaceControl.Builder::new,
+                mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new,
                 mMockSurfaceControlViewHostFactory);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
index 82e5a1c..62fb1c4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -27,10 +27,7 @@
 import android.annotation.NonNull;
 import android.graphics.Point;
 import android.graphics.Region;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.util.Size;
 
@@ -56,6 +53,8 @@
     private static final Size TASK_SIZE = new Size(500, 1000);
     private static final int TASK_CORNER_RADIUS = 10;
     private static final int EDGE_RESIZE_THICKNESS = 15;
+    private static final int EDGE_RESIZE_DEBUG_THICKNESS = EDGE_RESIZE_THICKNESS
+            + (DragResizeWindowGeometry.DEBUG ? DragResizeWindowGeometry.EDGE_DEBUG_BUFFER : 0);
     private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10;
     private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10;
     private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry(
@@ -72,7 +71,7 @@
             TASK_SIZE.getHeight() + EDGE_RESIZE_THICKNESS / 2);
 
     @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     /**
      * Check that both groups of objects satisfy equals/hashcode within each group, and that each
@@ -90,13 +89,14 @@
                                 EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE),
                         new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
                                 EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE))
-                .addEqualityGroup(
+                .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+                                EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5),
                         new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
-                                EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
-                                LARGE_CORNER_SIZE + 5),
+                                EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5))
+                .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+                                EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE),
                         new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
-                                EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
-                                LARGE_CORNER_SIZE + 5))
+                                EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE))
                 .testEquals();
     }
 
@@ -122,21 +122,21 @@
     private static void verifyHorizontalEdge(@NonNull Region region, @NonNull Point point) {
         assertThat(region.contains(point.x, point.y)).isTrue();
         // Horizontally along the edge is still contained.
-        assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isTrue();
-        assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isTrue();
+        assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue();
+        assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue();
         // Vertically along the edge is not contained.
-        assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isFalse();
-        assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isFalse();
+        assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isFalse();
+        assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isFalse();
     }
 
     private static void verifyVerticalEdge(@NonNull Region region, @NonNull Point point) {
         assertThat(region.contains(point.x, point.y)).isTrue();
         // Horizontally along the edge is not contained.
-        assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isFalse();
-        assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isFalse();
+        assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse();
+        assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse();
         // Vertically along the edge is contained.
-        assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isTrue();
-        assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isTrue();
+        assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isTrue();
+        assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isTrue();
     }
 
     /**
@@ -144,11 +144,14 @@
      * capture all eligible input regardless of source (touch or cursor).
      */
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
     public void testRegionUnion_edgeDragResizeEnabled_containsLargeCorners() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE);
         Region region = new Region();
         GEOMETRY.union(region);
-        final int cornerRadius = LARGE_CORNER_SIZE / 2;
+        // Make sure we're choosing a point outside of any debug region buffer.
+        final int cornerRadius = DragResizeWindowGeometry.DEBUG
+                ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS)
+                : LARGE_CORNER_SIZE / 2;
 
         new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
     }
@@ -158,26 +161,28 @@
      * size.
      */
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
     public void testRegionUnion_edgeDragResizeDisabled_containsFineCorners() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE);
         Region region = new Region();
         GEOMETRY.union(region);
-        final int cornerRadius = FINE_CORNER_SIZE / 2;
+        final int cornerRadius = DragResizeWindowGeometry.DEBUG
+                ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS)
+                : LARGE_CORNER_SIZE / 2;
 
         new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
     public void testCalculateControlType_edgeDragResizeEnabled_edges() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE);
         // The input source (touch or cursor) shouldn't impact the edge resize size.
         validateCtrlTypeForEdges(/* isTouch= */ false);
         validateCtrlTypeForEdges(/* isTouch= */ true);
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
     public void testCalculateControlType_edgeDragResizeDisabled_edges() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE);
         // Edge resizing is not supported when the flag is disabled.
         validateCtrlTypeForEdges(/* isTouch= */ false);
         validateCtrlTypeForEdges(/* isTouch= */ false);
@@ -195,8 +200,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
     public void testCalculateControlType_edgeDragResizeEnabled_corners() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE);
         final TestPoints fineTestPoints = new TestPoints(TASK_SIZE, FINE_CORNER_SIZE / 2);
         final TestPoints largeCornerTestPoints = new TestPoints(TASK_SIZE, LARGE_CORNER_SIZE / 2);
 
@@ -218,8 +223,8 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
     public void testCalculateControlType_edgeDragResizeDisabled_corners() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE);
         final TestPoints fineTestPoints = new TestPoints(TASK_SIZE, FINE_CORNER_SIZE / 2);
         final TestPoints largeCornerTestPoints = new TestPoints(TASK_SIZE, LARGE_CORNER_SIZE / 2);
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepositoryParameterizedTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepositoryParameterizedTests.kt
new file mode 100644
index 0000000..a9fddc6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepositoryParameterizedTests.kt
@@ -0,0 +1,150 @@
+/*
+ * 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.content.Context
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags
+import java.util.function.Consumer
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+/**
+ * Tests for {@link ResizeHandleSizeRepository}.
+ *
+ * Build/Install/Run: atest WMShellUnitTests:ResizeHandleSizeRepositoryParameterizedTests
+ */
+@SmallTest
+@RunWith(Parameterized::class)
+class ResizeHandleSizeRepositoryParameterizedTests {
+    private val resources = ApplicationProvider.getApplicationContext<Context>().resources
+    private val resizeHandleSizeRepository = ResizeHandleSizeRepository()
+    @Mock private lateinit var mockSizeChangeFunctionOne: Consumer<ResizeHandleSizeRepository>
+    @Mock private lateinit var mockSizeChangeFunctionTwo: Consumer<ResizeHandleSizeRepository>
+
+    @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+    @Parameter(0) lateinit var name: String
+    // The current ResizeHandleSizeRepository API under test.
+
+    @Parameter(1) lateinit var operation: (ResizeHandleSizeRepository) -> Unit
+
+    @Before
+    fun setOverrideBeforeResetResizeHandle() {
+        MockitoAnnotations.initMocks(this)
+        if (name != "reset") return
+        val originalEdgeHandle =
+            resizeHandleSizeRepository.getResizeEdgeHandlePixels(resources)
+        resizeHandleSizeRepository.setResizeEdgeHandlePixels(originalEdgeHandle + 2)
+    }
+
+    companion object {
+        @Parameters(name = "{index}: {0}")
+        @JvmStatic
+        fun data(): Iterable<Array<Any>> {
+            return listOf(
+                arrayOf(
+                    "reset",
+                    { sizeRepository: ResizeHandleSizeRepository ->
+                        sizeRepository.resetResizeEdgeHandlePixels()
+                    }
+                ),
+                arrayOf(
+                    "set",
+                    { sizeRepository: ResizeHandleSizeRepository ->
+                        sizeRepository.setResizeEdgeHandlePixels(99)
+                    }
+                )
+            )
+        }
+    }
+
+    // =================
+    // Validate that listeners are notified correctly for reset resize handle API.
+    // =================
+
+    @Test
+    fun testUpdateResizeHandleSize_flagDisabled() {
+        setFlagsRule.disableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+        registerSizeChangeFunctions()
+        operation.invoke(resizeHandleSizeRepository)
+        // Nothing is notified since flag is disabled.
+        verify(mockSizeChangeFunctionOne, never()).accept(any())
+        verify(mockSizeChangeFunctionTwo, never()).accept(any())
+    }
+
+    @Test
+    fun testUpdateResizeHandleSize_flagEnabled_noListeners() {
+        setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+        operation.invoke(resizeHandleSizeRepository)
+        // Nothing is notified since nothing was registered.
+        verify(mockSizeChangeFunctionOne, never()).accept(any())
+        verify(mockSizeChangeFunctionTwo, never()).accept(any())
+    }
+
+    @Test
+    fun testUpdateResizeHandleSize_flagEnabled_listenersNotified() {
+        setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+        registerSizeChangeFunctions()
+        operation.invoke(resizeHandleSizeRepository)
+        // Functions notified when reset.
+        verify(mockSizeChangeFunctionOne).accept(any())
+        verify(mockSizeChangeFunctionTwo).accept(any())
+    }
+
+    @Test
+    fun testUpdateResizeHandleSize_flagEnabled_listenerFails() {
+        setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+        registerSizeChangeFunctions()
+        operation.invoke(resizeHandleSizeRepository)
+        // Functions notified when reset.
+        verify(mockSizeChangeFunctionOne).accept(any())
+        verify(mockSizeChangeFunctionTwo).accept(any())
+    }
+
+    @Test
+    fun testUpdateResizeHandleSize_flagEnabled_ignoreSecondListener() {
+        setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+        registerSizeChangeFunctions()
+        val extraConsumerMock = mock(Consumer::class.java) as Consumer<ResizeHandleSizeRepository>
+        resizeHandleSizeRepository.registerSizeChangeFunction(extraConsumerMock)
+        // First listener succeeds, second one that fails is ignored.
+        operation.invoke(resizeHandleSizeRepository)
+        // Functions notified when reset.
+        verify(mockSizeChangeFunctionOne).accept(any())
+        verify(mockSizeChangeFunctionTwo).accept(any())
+        verify(extraConsumerMock).accept(any())
+    }
+
+    private fun registerSizeChangeFunctions() {
+        resizeHandleSizeRepository.registerSizeChangeFunction(mockSizeChangeFunctionOne)
+        resizeHandleSizeRepository.registerSizeChangeFunction(mockSizeChangeFunctionTwo)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepositoryTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepositoryTests.kt
new file mode 100644
index 0000000..59bbc72
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepositoryTests.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.content.Context
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.AndroidTestingRunner
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for {@link ResizeHandleSizeRepository}. Validate that get/reset/set work correctly.
+ *
+ * Build/Install/Run: atest WMShellUnitTests:ResizeHandleSizeRepositoryTests
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ResizeHandleSizeRepositoryTests {
+    private val resources = ApplicationProvider.getApplicationContext<Context>().resources
+    private val resizeHandleSizeRepository = ResizeHandleSizeRepository()
+
+    @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+    @Test
+    fun testOverrideResizeEdgeHandlePixels_flagEnabled_resetSucceeds() {
+        setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+        // Reset does nothing when no override is set.
+        val originalEdgeHandle =
+            resizeHandleSizeRepository.getResizeEdgeHandlePixels(resources)
+        resizeHandleSizeRepository.resetResizeEdgeHandlePixels()
+        assertThat(resizeHandleSizeRepository.getResizeEdgeHandlePixels(resources))
+            .isEqualTo(originalEdgeHandle)
+
+        // Now try to set the value; reset should succeed.
+        resizeHandleSizeRepository.setResizeEdgeHandlePixels(originalEdgeHandle + 2)
+        resizeHandleSizeRepository.resetResizeEdgeHandlePixels()
+        assertThat(resizeHandleSizeRepository.getResizeEdgeHandlePixels(resources))
+            .isEqualTo(originalEdgeHandle)
+    }
+
+    @Test
+    fun testOverrideResizeEdgeHandlePixels_flagDisabled_resetFails() {
+        setFlagsRule.disableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+        // Reset does nothing when no override is set.
+        val originalEdgeHandle =
+            resizeHandleSizeRepository.getResizeEdgeHandlePixels(resources)
+        resizeHandleSizeRepository.resetResizeEdgeHandlePixels()
+        assertThat(resizeHandleSizeRepository.getResizeEdgeHandlePixels(resources))
+            .isEqualTo(originalEdgeHandle)
+
+        // Now try to set the value; reset should do nothing.
+        val newEdgeHandle = originalEdgeHandle + 2
+        resizeHandleSizeRepository.setResizeEdgeHandlePixels(newEdgeHandle)
+        resizeHandleSizeRepository.resetResizeEdgeHandlePixels()
+        assertThat(resizeHandleSizeRepository.getResizeEdgeHandlePixels(resources))
+            .isEqualTo(originalEdgeHandle)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 4eb44d7..8b8cd11 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -75,7 +75,7 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
 import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.DesktopModeStatus;
 import com.android.wm.shell.tests.R;
 
 import org.junit.Before;
diff --git a/libs/androidfw/fuzz/resourcefile_fuzzer/Android.bp b/libs/androidfw/fuzz/resourcefile_fuzzer/Android.bp
index b511244..6196589 100644
--- a/libs/androidfw/fuzz/resourcefile_fuzzer/Android.bp
+++ b/libs/androidfw/fuzz/resourcefile_fuzzer/Android.bp
@@ -19,6 +19,7 @@
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
     default_applicable_licenses: ["frameworks_base_libs_androidfw_license"],
+    default_team: "trendy_team_android_resources",
 }
 
 cc_fuzz {
@@ -31,7 +32,7 @@
     static_libs: ["libgmock"],
     target: {
         android: {
-            shared_libs:[
+            shared_libs: [
                 "libandroidfw",
                 "libbase",
                 "libcutils",
@@ -52,4 +53,15 @@
             ],
         },
     },
+    fuzz_config: {
+        cc: [
+            "android-resources@google.com",
+        ],
+        componentid: 568761,
+        description: "The fuzzer targets the APIs of libandroidfw",
+        vector: "local_no_privileges_required",
+        service_privilege: "privileged",
+        users: "multi_user",
+        fuzzed_code_usage: "shipped",
+    },
 }
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 753a699..7c1c5b4 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -336,6 +336,7 @@
         "jni/android_graphics_animation_NativeInterpolatorFactory.cpp",
         "jni/android_graphics_animation_RenderNodeAnimator.cpp",
         "jni/android_graphics_Canvas.cpp",
+        "jni/android_graphics_Color.cpp",
         "jni/android_graphics_ColorSpace.cpp",
         "jni/android_graphics_drawable_AnimatedVectorDrawable.cpp",
         "jni/android_graphics_drawable_VectorDrawable.cpp",
diff --git a/libs/hwui/ColorFilter.h b/libs/hwui/ColorFilter.h
index 1a5b938..31c9db7 100644
--- a/libs/hwui/ColorFilter.h
+++ b/libs/hwui/ColorFilter.h
@@ -23,17 +23,42 @@
 
 #include "GraphicsJNI.h"
 #include "SkColorFilter.h"
-#include "SkiaWrapper.h"
 
 namespace android {
 namespace uirenderer {
 
-class ColorFilter : public SkiaWrapper<SkColorFilter> {
+class ColorFilter : public VirtualLightRefBase {
 public:
     static ColorFilter* fromJava(jlong handle) { return reinterpret_cast<ColorFilter*>(handle); }
 
+    sk_sp<SkColorFilter> getInstance() {
+        if (mInstance != nullptr && shouldDiscardInstance()) {
+            mInstance = nullptr;
+        }
+
+        if (mInstance == nullptr) {
+            mInstance = createInstance();
+            if (mInstance) {
+                mInstance = mInstance->makeWithWorkingColorSpace(SkColorSpace::MakeSRGB());
+            }
+            mGenerationId++;
+        }
+        return mInstance;
+    }
+
+    virtual bool shouldDiscardInstance() const { return false; }
+
+    void discardInstance() { mInstance = nullptr; }
+
+    [[nodiscard]] int32_t getGenerationId() const { return mGenerationId; }
+
 protected:
     ColorFilter() = default;
+    virtual sk_sp<SkColorFilter> createInstance() = 0;
+
+private:
+    sk_sp<SkColorFilter> mInstance = nullptr;
+    int32_t mGenerationId = 0;
 };
 
 class BlendModeColorFilter : public ColorFilter {
diff --git a/libs/hwui/SkiaWrapper.h b/libs/hwui/SkiaWrapper.h
deleted file mode 100644
index bd0e35a..0000000
--- a/libs/hwui/SkiaWrapper.h
+++ /dev/null
@@ -1,56 +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.
- */
-
-#ifndef SKIA_WRAPPER_H_
-#define SKIA_WRAPPER_H_
-
-#include <SkRefCnt.h>
-#include <utils/RefBase.h>
-
-namespace android::uirenderer {
-
-template <typename T>
-class SkiaWrapper : public VirtualLightRefBase {
-public:
-    sk_sp<T> getInstance() {
-        if (mInstance != nullptr && shouldDiscardInstance()) {
-            mInstance = nullptr;
-        }
-
-        if (mInstance == nullptr) {
-            mInstance = createInstance();
-            mGenerationId++;
-        }
-        return mInstance;
-    }
-
-    virtual bool shouldDiscardInstance() const { return false; }
-
-    void discardInstance() { mInstance = nullptr; }
-
-    [[nodiscard]] int32_t getGenerationId() const { return mGenerationId; }
-
-protected:
-    virtual sk_sp<T> createInstance() = 0;
-
-private:
-    sk_sp<T> mInstance = nullptr;
-    int32_t mGenerationId = 0;
-};
-
-}  // namespace android::uirenderer
-
-#endif  // SKIA_WRAPPER_H_
diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp
index fd9915a..70a9ef0 100644
--- a/libs/hwui/apex/LayoutlibLoader.cpp
+++ b/libs/hwui/apex/LayoutlibLoader.cpp
@@ -46,6 +46,7 @@
 
 extern int register_android_graphics_Canvas(JNIEnv* env);
 extern int register_android_graphics_CanvasProperty(JNIEnv* env);
+extern int register_android_graphics_Color(JNIEnv* env);
 extern int register_android_graphics_ColorFilter(JNIEnv* env);
 extern int register_android_graphics_ColorSpace(JNIEnv* env);
 extern int register_android_graphics_DrawFilter(JNIEnv* env);
@@ -87,6 +88,7 @@
         {"android.graphics.Camera", REG_JNI(register_android_graphics_Camera)},
         {"android.graphics.Canvas", REG_JNI(register_android_graphics_Canvas)},
         {"android.graphics.CanvasProperty", REG_JNI(register_android_graphics_CanvasProperty)},
+        {"android.graphics.Color", REG_JNI(register_android_graphics_Color)},
         {"android.graphics.ColorFilter", REG_JNI(register_android_graphics_ColorFilter)},
         {"android.graphics.ColorSpace", REG_JNI(register_android_graphics_ColorSpace)},
         {"android.graphics.CreateJavaOutputStreamAdaptor",
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index fb0cdb0..6ace396 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -49,6 +49,7 @@
 extern int register_android_graphics_Canvas(JNIEnv* env);
 extern int register_android_graphics_CanvasProperty(JNIEnv* env);
 extern int register_android_graphics_ColorFilter(JNIEnv* env);
+extern int register_android_graphics_Color(JNIEnv* env);
 extern int register_android_graphics_ColorSpace(JNIEnv* env);
 extern int register_android_graphics_DrawFilter(JNIEnv* env);
 extern int register_android_graphics_FontFamily(JNIEnv* env);
@@ -98,6 +99,7 @@
 
     static const RegJNIRec gRegJNI[] = {
             REG_JNI(register_android_graphics_Canvas),
+            REG_JNI(register_android_graphics_Color),
             // This needs to be before register_android_graphics_Graphics, or the latter
             // will not be able to find the jmethodID for ColorSpace.get().
             REG_JNI(register_android_graphics_ColorSpace),
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index a952be0..2a057e7 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -36,25 +36,6 @@
         return 0;                   \
     }
 
-static void Color_RGBToHSV(JNIEnv* env, jobject, jint red, jint green, jint blue, jfloatArray hsvArray)
-{
-    SkScalar hsv[3];
-    SkRGBToHSV(red, green, blue, hsv);
-
-    AutoJavaFloatArray  autoHSV(env, hsvArray, 3);
-    float* values = autoHSV.ptr();
-    for (int i = 0; i < 3; i++) {
-        values[i] = SkScalarToFloat(hsv[i]);
-    }
-}
-
-static jint Color_HSVToColor(JNIEnv* env, jobject, jint alpha, jfloatArray hsvArray)
-{
-    AutoJavaFloatArray  autoHSV(env, hsvArray, 3);
-    SkScalar* hsv = autoHSV.ptr();
-    return static_cast<jint>(SkHSVToColor(alpha, hsv));
-}
-
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
 static void Shader_safeUnref(SkShader* shader) {
@@ -409,11 +390,6 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-static const JNINativeMethod gColorMethods[] = {
-    { "nativeRGBToHSV",    "(III[F)V", (void*)Color_RGBToHSV   },
-    { "nativeHSVToColor",  "(I[F)I",   (void*)Color_HSVToColor }
-};
-
 static const JNINativeMethod gShaderMethods[] = {
     { "nativeGetFinalizer",   "()J",    (void*)Shader_getNativeFinalizer },
 };
@@ -456,8 +432,6 @@
 
 int register_android_graphics_Shader(JNIEnv* env)
 {
-    android::RegisterMethodsOrDie(env, "android/graphics/Color", gColorMethods,
-                                  NELEM(gColorMethods));
     android::RegisterMethodsOrDie(env, "android/graphics/Shader", gShaderMethods,
                                   NELEM(gShaderMethods));
     android::RegisterMethodsOrDie(env, "android/graphics/BitmapShader", gBitmapShaderMethods,
diff --git a/libs/hwui/jni/android_graphics_Color.cpp b/libs/hwui/jni/android_graphics_Color.cpp
new file mode 100644
index 0000000..c22b8b9
--- /dev/null
+++ b/libs/hwui/jni/android_graphics_Color.cpp
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+#include "GraphicsJNI.h"
+
+#include "SkColor.h"
+
+using namespace android;
+
+static void Color_RGBToHSV(JNIEnv* env, jobject, jint red, jint green, jint blue,
+                           jfloatArray hsvArray)
+{
+    SkScalar hsv[3];
+    SkRGBToHSV(red, green, blue, hsv);
+
+    AutoJavaFloatArray  autoHSV(env, hsvArray, 3);
+    float* values = autoHSV.ptr();
+    for (int i = 0; i < 3; i++) {
+        values[i] = SkScalarToFloat(hsv[i]);
+    }
+}
+
+static jint Color_HSVToColor(JNIEnv* env, jobject, jint alpha, jfloatArray hsvArray)
+{
+    AutoJavaFloatArray  autoHSV(env, hsvArray, 3);
+    SkScalar* hsv = autoHSV.ptr();
+    return static_cast<jint>(SkHSVToColor(alpha, hsv));
+}
+
+static const JNINativeMethod gColorMethods[] = {
+    { "nativeRGBToHSV",    "(III[F)V", (void*)Color_RGBToHSV   },
+    { "nativeHSVToColor",  "(I[F)I",   (void*)Color_HSVToColor }
+};
+
+namespace android {
+
+int register_android_graphics_Color(JNIEnv* env) {
+    return android::RegisterMethodsOrDie(env, "android/graphics/Color", gColorMethods,
+                                         NELEM(gColorMethods));
+}
+
+}; // namespace android
diff --git a/libs/hwui/jni/android_graphics_ColorSpace.cpp b/libs/hwui/jni/android_graphics_ColorSpace.cpp
index 63d3f83..d06206b 100644
--- a/libs/hwui/jni/android_graphics_ColorSpace.cpp
+++ b/libs/hwui/jni/android_graphics_ColorSpace.cpp
@@ -148,7 +148,7 @@
 namespace android {
 
 int register_android_graphics_ColorSpace(JNIEnv* env) {
-    return android::RegisterMethodsOrDie(env, "android/graphics/ColorSpace$Rgb",
+    return android::RegisterMethodsOrDie(env, "android/graphics/ColorSpace$Rgb$Native",
                                          gColorSpaceRgbMethods, NELEM(gColorSpaceRgbMethods));
 }
 
diff --git a/libs/hwui/jni/android_graphics_Matrix.cpp b/libs/hwui/jni/android_graphics_Matrix.cpp
index c0d791a..eedc069 100644
--- a/libs/hwui/jni/android_graphics_Matrix.cpp
+++ b/libs/hwui/jni/android_graphics_Matrix.cpp
@@ -326,9 +326,6 @@
 };
 
 static const JNINativeMethod methods[] = {
-    {"nGetNativeFinalizer", "()J", (void*) SkMatrixGlue::getNativeFinalizer},
-    {"nCreate","(J)J", (void*) SkMatrixGlue::create},
-
     // ------- @FastNative below here ---------------
     {"nMapPoints","(J[FI[FIIZ)V", (void*) SkMatrixGlue::mapPoints},
     {"nMapRect","(JLandroid/graphics/RectF;Landroid/graphics/RectF;)Z",
@@ -388,9 +385,6 @@
 int register_android_graphics_Matrix(JNIEnv* env) {
     // Methods only used on Ravenwood (for now). See the javadoc on Matrix$ExtraNativesx
     // for why we need it.
-    //
-    // We don't need it on non-ravenwood, but we don't (yet) have a way to detect ravenwood
-    // environment, so we just always run it.
     RegisterMethodsOrDie(env, "android/graphics/Matrix$ExtraNatives", extra_methods,
                          NELEM(extra_methods));
 
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index 6a46544..f1ee325 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -117,7 +117,7 @@
     return {mLocked.pointerX, mLocked.pointerY};
 }
 
-int32_t MouseCursorController::getDisplayId() const {
+ui::LogicalDisplayId MouseCursorController::getDisplayId() const {
     std::scoped_lock lock(mLock);
     return mLocked.viewport.displayId;
 }
@@ -467,10 +467,10 @@
 
     std::function<bool(nsecs_t)> func = std::bind(&MouseCursorController::doAnimations, this, _1);
     /*
-     * Using -1 for displayId here to avoid removing the callback
+     * Using ui::LogicalDisplayId::INVALID for displayId here to avoid removing the callback
      * if a TouchSpotController with the same display is removed.
      */
-    mContext.addAnimationCallback(-1, func);
+    mContext.addAnimationCallback(ui::LogicalDisplayId::INVALID, func);
 }
 
 } // namespace android
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index 00dc085..dc7e8ca 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -47,7 +47,7 @@
     void move(float deltaX, float deltaY);
     void setPosition(float x, float y);
     FloatPoint getPosition() const;
-    int32_t getDisplayId() const;
+    ui::LogicalDisplayId getDisplayId() const;
     void fade(PointerControllerInterface::Transition transition);
     void unfade(PointerControllerInterface::Transition transition);
     void setDisplayViewport(const DisplayViewport& viewport, bool getAdditionalMouseResources);
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index f9dc5fa..cca1b07 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -24,7 +24,6 @@
 #include <SkColor.h>
 #include <android-base/stringprintf.h>
 #include <android-base/thread_annotations.h>
-#include <com_android_input_flags.h>
 #include <ftl/enum.h>
 
 #include <mutex>
@@ -35,14 +34,10 @@
 #define INDENT2 "    "
 #define INDENT3 "      "
 
-namespace input_flags = com::android::input::flags;
-
 namespace android {
 
 namespace {
 
-static const bool ENABLE_POINTER_CHOREOGRAPHER = input_flags::enable_pointer_choreographer();
-
 const ui::Transform kIdentityTransform;
 
 } // namespace
@@ -68,27 +63,24 @@
 
 std::shared_ptr<PointerController> PointerController::create(
         const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
-        SpriteController& spriteController, bool enabled, ControllerType type) {
+        SpriteController& spriteController, ControllerType type) {
     // using 'new' to access non-public constructor
     std::shared_ptr<PointerController> controller;
     switch (type) {
         case ControllerType::MOUSE:
             controller = std::shared_ptr<PointerController>(
-                    new MousePointerController(policy, looper, spriteController, enabled));
+                    new MousePointerController(policy, looper, spriteController));
             break;
         case ControllerType::TOUCH:
             controller = std::shared_ptr<PointerController>(
-                    new TouchPointerController(policy, looper, spriteController, enabled));
+                    new TouchPointerController(policy, looper, spriteController));
             break;
         case ControllerType::STYLUS:
             controller = std::shared_ptr<PointerController>(
-                    new StylusPointerController(policy, looper, spriteController, enabled));
+                    new StylusPointerController(policy, looper, spriteController));
             break;
-        case ControllerType::LEGACY:
         default:
-            controller = std::shared_ptr<PointerController>(
-                    new PointerController(policy, looper, spriteController, enabled));
-            break;
+            LOG_ALWAYS_FATAL("Invalid ControllerType: %d", static_cast<int>(type));
     }
 
     /*
@@ -108,10 +100,9 @@
 }
 
 PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
-                                     const sp<Looper>& looper, SpriteController& spriteController,
-                                     bool enabled)
+                                     const sp<Looper>& looper, SpriteController& spriteController)
       : PointerController(
-                policy, looper, spriteController, enabled,
+                policy, looper, spriteController,
                 [](const sp<android::gui::WindowInfosListener>& listener) {
                     auto initialInfo = std::make_pair(std::vector<android::gui::WindowInfo>{},
                                                       std::vector<android::gui::DisplayInfo>{});
@@ -125,11 +116,9 @@
 
 PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
                                      const sp<Looper>& looper, SpriteController& spriteController,
-                                     bool enabled,
                                      const WindowListenerRegisterConsumer& registerListener,
                                      WindowListenerUnregisterConsumer unregisterListener)
-      : mEnabled(enabled),
-        mContext(policy, looper, spriteController, *this),
+      : mContext(policy, looper, spriteController, *this),
         mCursorController(mContext),
         mDisplayInfoListener(sp<DisplayInfoListener>::make(this)),
         mUnregisterWindowInfosListener(std::move(unregisterListener)) {
@@ -142,7 +131,6 @@
 PointerController::~PointerController() {
     mDisplayInfoListener->onPointerControllerDestroyed();
     mUnregisterWindowInfosListener(mDisplayInfoListener);
-    mContext.getPolicy()->onPointerDisplayIdChanged(ADISPLAY_ID_NONE, FloatPoint{0, 0});
 }
 
 std::mutex& PointerController::getLock() const {
@@ -150,15 +138,11 @@
 }
 
 std::optional<FloatRect> PointerController::getBounds() const {
-    if (!mEnabled) return {};
-
     return mCursorController.getBounds();
 }
 
 void PointerController::move(float deltaX, float deltaY) {
-    if (!mEnabled) return;
-
-    const int32_t displayId = mCursorController.getDisplayId();
+    const ui::LogicalDisplayId displayId = mCursorController.getDisplayId();
     vec2 transformed;
     {
         std::scoped_lock lock(getLock());
@@ -169,9 +153,7 @@
 }
 
 void PointerController::setPosition(float x, float y) {
-    if (!mEnabled) return;
-
-    const int32_t displayId = mCursorController.getDisplayId();
+    const ui::LogicalDisplayId displayId = mCursorController.getDisplayId();
     vec2 transformed;
     {
         std::scoped_lock lock(getLock());
@@ -182,11 +164,7 @@
 }
 
 FloatPoint PointerController::getPosition() const {
-    if (!mEnabled) {
-        return FloatPoint{0, 0};
-    }
-
-    const int32_t displayId = mCursorController.getDisplayId();
+    const ui::LogicalDisplayId displayId = mCursorController.getDisplayId();
     const auto p = mCursorController.getPosition();
     {
         std::scoped_lock lock(getLock());
@@ -195,29 +173,21 @@
     }
 }
 
-int32_t PointerController::getDisplayId() const {
-    if (!mEnabled) return ADISPLAY_ID_NONE;
-
+ui::LogicalDisplayId PointerController::getDisplayId() const {
     return mCursorController.getDisplayId();
 }
 
 void PointerController::fade(Transition transition) {
-    if (!mEnabled) return;
-
     std::scoped_lock lock(getLock());
     mCursorController.fade(transition);
 }
 
 void PointerController::unfade(Transition transition) {
-    if (!mEnabled) return;
-
     std::scoped_lock lock(getLock());
     mCursorController.unfade(transition);
 }
 
 void PointerController::setPresentation(Presentation presentation) {
-    if (!mEnabled) return;
-
     std::scoped_lock lock(getLock());
 
     if (mLocked.presentation == presentation) {
@@ -226,33 +196,13 @@
 
     mLocked.presentation = presentation;
 
-    if (ENABLE_POINTER_CHOREOGRAPHER) {
-        // When pointer choreographer is enabled, the presentation mode is only set once when the
-        // PointerController is constructed, before the display viewport is provided.
-        // TODO(b/293587049): Clean up the PointerController interface after pointer choreographer
-        // is permanently enabled. The presentation can be set in the constructor.
-        mCursorController.setStylusHoverMode(presentation == Presentation::STYLUS_HOVER);
-        return;
-    }
-
-    if (!mCursorController.isViewportValid()) {
-        return;
-    }
-
-    if (presentation == Presentation::POINTER || presentation == Presentation::STYLUS_HOVER) {
-        // For now, we support stylus hover using the mouse cursor implementation.
-        // TODO: Add proper support for stylus hover icons.
-        mCursorController.setStylusHoverMode(presentation == Presentation::STYLUS_HOVER);
-
-        mCursorController.getAdditionalMouseResources();
-        clearSpotsLocked();
-    }
+    // The presentation mode is only set once when the PointerController is constructed,
+    // before the display viewport is provided.
+    mCursorController.setStylusHoverMode(presentation == Presentation::STYLUS_HOVER);
 }
 
 void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
-                                 BitSet32 spotIdBits, int32_t displayId) {
-    if (!mEnabled) return;
-
+                                 BitSet32 spotIdBits, ui::LogicalDisplayId displayId) {
     std::scoped_lock lock(getLock());
     std::array<PointerCoords, MAX_POINTERS> outSpotCoords{};
     const ui::Transform& transform = getTransformForDisplayLocked(displayId);
@@ -272,12 +222,13 @@
     if (it == mLocked.spotControllers.end()) {
         mLocked.spotControllers.try_emplace(displayId, displayId, mContext);
     }
-    mLocked.spotControllers.at(displayId).setSpots(outSpotCoords.data(), spotIdToIndex, spotIdBits);
+    bool skipScreenshot = mLocked.displaysToSkipScreenshot.find(displayId) !=
+            mLocked.displaysToSkipScreenshot.end();
+    mLocked.spotControllers.at(displayId).setSpots(outSpotCoords.data(), spotIdToIndex, spotIdBits,
+                                                   skipScreenshot);
 }
 
 void PointerController::clearSpots() {
-    if (!mEnabled) return;
-
     std::scoped_lock lock(getLock());
     clearSpotsLocked();
 }
@@ -310,12 +261,6 @@
 }
 
 void PointerController::setDisplayViewport(const DisplayViewport& viewport) {
-    struct PointerDisplayChangeArgs {
-        int32_t displayId;
-        FloatPoint cursorPosition;
-    };
-    std::optional<PointerDisplayChangeArgs> pointerDisplayChanged;
-
     { // acquire lock
         std::scoped_lock lock(getLock());
 
@@ -327,44 +272,42 @@
         mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
         if (viewport.displayId != mLocked.pointerDisplayId) {
             mLocked.pointerDisplayId = viewport.displayId;
-            pointerDisplayChanged = {viewport.displayId, mCursorController.getPosition()};
         }
     } // release lock
-
-    if (pointerDisplayChanged) {
-        // Notify the policy without holding the pointer controller lock.
-        mContext.getPolicy()->onPointerDisplayIdChanged(pointerDisplayChanged->displayId,
-                                                        pointerDisplayChanged->cursorPosition);
-    }
 }
 
 void PointerController::updatePointerIcon(PointerIconStyle iconId) {
-    if (!mEnabled) return;
-
     std::scoped_lock lock(getLock());
     mCursorController.updatePointerIcon(iconId);
 }
 
 void PointerController::setCustomPointerIcon(const SpriteIcon& icon) {
-    if (!mEnabled) return;
-
     std::scoped_lock lock(getLock());
     mCursorController.setCustomPointerIcon(icon);
 }
 
+void PointerController::setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) {
+    std::scoped_lock lock(getLock());
+    if (skip) {
+        mLocked.displaysToSkipScreenshot.insert(displayId);
+    } else {
+        mLocked.displaysToSkipScreenshot.erase(displayId);
+    }
+}
+
 void PointerController::doInactivityTimeout() {
     fade(Transition::GRADUAL);
 }
 
 void PointerController::onDisplayViewportsUpdated(const std::vector<DisplayViewport>& viewports) {
-    std::unordered_set<int32_t> displayIdSet;
+    std::unordered_set<ui::LogicalDisplayId> displayIdSet;
     for (const DisplayViewport& viewport : viewports) {
         displayIdSet.insert(viewport.displayId);
     }
 
     std::scoped_lock lock(getLock());
     for (auto it = mLocked.spotControllers.begin(); it != mLocked.spotControllers.end();) {
-        int32_t displayId = it->first;
+        ui::LogicalDisplayId displayId = it->first;
         if (!displayIdSet.count(displayId)) {
             /*
              * Ensures that an in-progress animation won't dereference
@@ -383,7 +326,8 @@
     mLocked.mDisplayInfos = displayInfo;
 }
 
-const ui::Transform& PointerController::getTransformForDisplayLocked(int displayId) const {
+const ui::Transform& PointerController::getTransformForDisplayLocked(
+        ui::LogicalDisplayId displayId) const {
     const auto& di = mLocked.mDisplayInfos;
     auto it = std::find_if(di.begin(), di.end(), [displayId](const gui::DisplayInfo& info) {
         return info.displayId == displayId;
@@ -392,15 +336,12 @@
 }
 
 std::string PointerController::dump() {
-    if (!mEnabled) {
-        return INDENT "PointerController: DISABLED due to ongoing PointerChoreographer refactor\n";
-    }
-
     std::string dump = INDENT "PointerController:\n";
     std::scoped_lock lock(getLock());
     dump += StringPrintf(INDENT2 "Presentation: %s\n",
                          ftl::enum_string(mLocked.presentation).c_str());
-    dump += StringPrintf(INDENT2 "Pointer Display ID: %" PRIu32 "\n", mLocked.pointerDisplayId);
+    dump += StringPrintf(INDENT2 "Pointer Display ID: %s\n",
+                         mLocked.pointerDisplayId.toString().c_str());
     dump += StringPrintf(INDENT2 "Viewports:\n");
     for (const auto& info : mLocked.mDisplayInfos) {
         info.dump(dump, INDENT3);
@@ -416,8 +357,8 @@
 
 MousePointerController::MousePointerController(const sp<PointerControllerPolicyInterface>& policy,
                                                const sp<Looper>& looper,
-                                               SpriteController& spriteController, bool enabled)
-      : PointerController(policy, looper, spriteController, enabled) {
+                                               SpriteController& spriteController)
+      : PointerController(policy, looper, spriteController) {
     PointerController::setPresentation(Presentation::POINTER);
 }
 
@@ -429,8 +370,8 @@
 
 TouchPointerController::TouchPointerController(const sp<PointerControllerPolicyInterface>& policy,
                                                const sp<Looper>& looper,
-                                               SpriteController& spriteController, bool enabled)
-      : PointerController(policy, looper, spriteController, enabled) {
+                                               SpriteController& spriteController)
+      : PointerController(policy, looper, spriteController) {
     PointerController::setPresentation(Presentation::SPOT);
 }
 
@@ -442,8 +383,8 @@
 
 StylusPointerController::StylusPointerController(const sp<PointerControllerPolicyInterface>& policy,
                                                  const sp<Looper>& looper,
-                                                 SpriteController& spriteController, bool enabled)
-      : PointerController(policy, looper, spriteController, enabled) {
+                                                 SpriteController& spriteController)
+      : PointerController(policy, looper, spriteController) {
     PointerController::setPresentation(Presentation::STYLUS_HOVER);
 }
 
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 6ee5707..c6430f7 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -47,8 +47,7 @@
 public:
     static std::shared_ptr<PointerController> create(
             const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
-            SpriteController& spriteController, bool enabled,
-            ControllerType type = ControllerType::LEGACY);
+            SpriteController& spriteController, ControllerType type);
 
     ~PointerController() override;
 
@@ -56,17 +55,18 @@
     void move(float deltaX, float deltaY) override;
     void setPosition(float x, float y) override;
     FloatPoint getPosition() const override;
-    int32_t getDisplayId() const override;
+    ui::LogicalDisplayId getDisplayId() const override;
     void fade(Transition transition) override;
     void unfade(Transition transition) override;
     void setDisplayViewport(const DisplayViewport& viewport) override;
 
     void setPresentation(Presentation presentation) override;
     void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
-                  BitSet32 spotIdBits, int32_t displayId) override;
+                  BitSet32 spotIdBits, ui::LogicalDisplayId displayId) override;
     void clearSpots() override;
     void updatePointerIcon(PointerIconStyle iconId) override;
     void setCustomPointerIcon(const SpriteIcon& icon) override;
+    void setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) override;
 
     virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout);
     void doInactivityTimeout();
@@ -86,12 +86,12 @@
 
     // Constructor used to test WindowInfosListener registration.
     PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
-                      SpriteController& spriteController, bool enabled,
+                      SpriteController& spriteController,
                       const WindowListenerRegisterConsumer& registerListener,
                       WindowListenerUnregisterConsumer unregisterListener);
 
     PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
-                      SpriteController& spriteController, bool enabled);
+                      SpriteController& spriteController);
 
 private:
     friend PointerControllerContext::LooperCallback;
@@ -103,18 +103,17 @@
     // we use the DisplayInfoListener's lock in PointerController.
     std::mutex& getLock() const;
 
-    const bool mEnabled;
-
     PointerControllerContext mContext;
 
     MouseCursorController mCursorController;
 
     struct Locked {
         Presentation presentation;
-        int32_t pointerDisplayId = ADISPLAY_ID_NONE;
+        ui::LogicalDisplayId pointerDisplayId = ui::LogicalDisplayId::INVALID;
 
         std::vector<gui::DisplayInfo> mDisplayInfos;
-        std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers;
+        std::unordered_map<ui::LogicalDisplayId, TouchSpotController> spotControllers;
+        std::unordered_set<ui::LogicalDisplayId> displaysToSkipScreenshot;
     } mLocked GUARDED_BY(getLock());
 
     class DisplayInfoListener : public gui::WindowInfosListener {
@@ -133,7 +132,8 @@
     sp<DisplayInfoListener> mDisplayInfoListener;
     const WindowListenerUnregisterConsumer mUnregisterWindowInfosListener;
 
-    const ui::Transform& getTransformForDisplayLocked(int displayId) const REQUIRES(getLock());
+    const ui::Transform& getTransformForDisplayLocked(ui::LogicalDisplayId displayId) const
+            REQUIRES(getLock());
 
     void clearSpotsLocked() REQUIRES(getLock());
 };
@@ -142,15 +142,14 @@
 public:
     /** A version of PointerController that controls one mouse pointer. */
     MousePointerController(const sp<PointerControllerPolicyInterface>& policy,
-                           const sp<Looper>& looper, SpriteController& spriteController,
-                           bool enabled);
+                           const sp<Looper>& looper, SpriteController& spriteController);
 
     ~MousePointerController() override;
 
     void setPresentation(Presentation) override {
         LOG_ALWAYS_FATAL("Should not be called");
     }
-    void setSpots(const PointerCoords*, const uint32_t*, BitSet32, int32_t) override {
+    void setSpots(const PointerCoords*, const uint32_t*, BitSet32, ui::LogicalDisplayId) override {
         LOG_ALWAYS_FATAL("Should not be called");
     }
     void clearSpots() override {
@@ -162,8 +161,7 @@
 public:
     /** A version of PointerController that controls touch spots. */
     TouchPointerController(const sp<PointerControllerPolicyInterface>& policy,
-                           const sp<Looper>& looper, SpriteController& spriteController,
-                           bool enabled);
+                           const sp<Looper>& looper, SpriteController& spriteController);
 
     ~TouchPointerController() override;
 
@@ -179,7 +177,7 @@
     FloatPoint getPosition() const override {
         LOG_ALWAYS_FATAL("Should not be called");
     }
-    int32_t getDisplayId() const override {
+    ui::LogicalDisplayId getDisplayId() const override {
         LOG_ALWAYS_FATAL("Should not be called");
     }
     void fade(Transition) override {
@@ -208,15 +206,14 @@
 public:
     /** A version of PointerController that controls one stylus pointer. */
     StylusPointerController(const sp<PointerControllerPolicyInterface>& policy,
-                            const sp<Looper>& looper, SpriteController& spriteController,
-                            bool enabled);
+                            const sp<Looper>& looper, SpriteController& spriteController);
 
     ~StylusPointerController() override;
 
     void setPresentation(Presentation) override {
         LOG_ALWAYS_FATAL("Should not be called");
     }
-    void setSpots(const PointerCoords*, const uint32_t*, BitSet32, int32_t) override {
+    void setSpots(const PointerCoords*, const uint32_t*, BitSet32, ui::LogicalDisplayId) override {
         LOG_ALWAYS_FATAL("Should not be called");
     }
     void clearSpots() override {
diff --git a/libs/input/PointerControllerContext.cpp b/libs/input/PointerControllerContext.cpp
index 15c3517..747eb8e 100644
--- a/libs/input/PointerControllerContext.cpp
+++ b/libs/input/PointerControllerContext.cpp
@@ -138,12 +138,12 @@
     return 1; // keep the callback
 }
 
-void PointerControllerContext::addAnimationCallback(int32_t displayId,
+void PointerControllerContext::addAnimationCallback(ui::LogicalDisplayId displayId,
                                                     std::function<bool(nsecs_t)> callback) {
     mAnimator.addCallback(displayId, callback);
 }
 
-void PointerControllerContext::removeAnimationCallback(int32_t displayId) {
+void PointerControllerContext::removeAnimationCallback(ui::LogicalDisplayId displayId) {
     mAnimator.removeCallback(displayId);
 }
 
@@ -161,14 +161,14 @@
     }
 }
 
-void PointerControllerContext::PointerAnimator::addCallback(int32_t displayId,
+void PointerControllerContext::PointerAnimator::addCallback(ui::LogicalDisplayId displayId,
                                                             std::function<bool(nsecs_t)> callback) {
     std::scoped_lock lock(mLock);
     mLocked.callbacks[displayId] = callback;
     startAnimationLocked();
 }
 
-void PointerControllerContext::PointerAnimator::removeCallback(int32_t displayId) {
+void PointerControllerContext::PointerAnimator::removeCallback(ui::LogicalDisplayId displayId) {
     std::scoped_lock lock(mLock);
     auto it = mLocked.callbacks.find(displayId);
     if (it == mLocked.callbacks.end()) {
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
index 98c3988..d422148 100644
--- a/libs/input/PointerControllerContext.h
+++ b/libs/input/PointerControllerContext.h
@@ -72,16 +72,16 @@
     virtual ~PointerControllerPolicyInterface() {}
 
 public:
-    virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) = 0;
-    virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) = 0;
+    virtual void loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId) = 0;
+    virtual void loadPointerResources(PointerResources* outResources,
+                                      ui::LogicalDisplayId displayId) = 0;
     virtual void loadAdditionalMouseResources(
             std::map<PointerIconStyle, SpriteIcon>* outResources,
             std::map<PointerIconStyle, PointerAnimation>* outAnimationResources,
-            int32_t displayId) = 0;
+            ui::LogicalDisplayId displayId) = 0;
     virtual PointerIconStyle getDefaultPointerIconId() = 0;
     virtual PointerIconStyle getDefaultStylusIconId() = 0;
     virtual PointerIconStyle getCustomPointerIconId() = 0;
-    virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) = 0;
 };
 
 /*
@@ -103,7 +103,7 @@
 
     nsecs_t getAnimationTime();
 
-    void clearSpotsByDisplay(int32_t displayId);
+    void clearSpotsByDisplay(ui::LogicalDisplayId displayId);
 
     void setHandlerController(std::shared_ptr<PointerController> controller);
     void setCallbackController(std::shared_ptr<PointerController> controller);
@@ -113,8 +113,9 @@
 
     void handleDisplayEvents();
 
-    void addAnimationCallback(int32_t displayId, std::function<bool(nsecs_t)> callback);
-    void removeAnimationCallback(int32_t displayId);
+    void addAnimationCallback(ui::LogicalDisplayId displayId,
+                              std::function<bool(nsecs_t)> callback);
+    void removeAnimationCallback(ui::LogicalDisplayId displayId);
 
     class MessageHandler : public virtual android::MessageHandler {
     public:
@@ -137,8 +138,8 @@
     public:
         PointerAnimator(PointerControllerContext& context);
 
-        void addCallback(int32_t displayId, std::function<bool(nsecs_t)> callback);
-        void removeCallback(int32_t displayId);
+        void addCallback(ui::LogicalDisplayId displayId, std::function<bool(nsecs_t)> callback);
+        void removeCallback(ui::LogicalDisplayId displayId);
         void handleVsyncEvents();
         nsecs_t getAnimationTimeLocked();
 
@@ -149,7 +150,7 @@
             bool animationPending{false};
             nsecs_t animationTime{systemTime(SYSTEM_TIME_MONOTONIC)};
 
-            std::unordered_map<int32_t, std::function<bool(nsecs_t)>> callbacks;
+            std::unordered_map<ui::LogicalDisplayId, std::function<bool(nsecs_t)>> callbacks;
         } mLocked GUARDED_BY(mLock);
 
         DisplayEventReceiver mDisplayEventReceiver;
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index a63453d..af49939 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -19,9 +19,9 @@
 
 #include "SpriteController.h"
 
-#include <log/log.h>
-#include <utils/String8.h>
+#include <android-base/logging.h>
 #include <gui/Surface.h>
+#include <utils/String8.h>
 
 namespace android {
 
@@ -129,7 +129,7 @@
             update.state.surfaceVisible = false;
             update.state.surfaceControl =
                     obtainSurface(update.state.surfaceWidth, update.state.surfaceHeight,
-                                  update.state.displayId);
+                                  update.state.displayId, update.state.skipScreenshot);
             if (update.state.surfaceControl != NULL) {
                 update.surfaceChanged = surfaceChanged = true;
             }
@@ -209,7 +209,7 @@
               (update.state.dirty &
                (DIRTY_ALPHA | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER |
                 DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID | DIRTY_ICON_STYLE |
-                DIRTY_DRAW_DROP_SHADOW))))) {
+                DIRTY_DRAW_DROP_SHADOW | DIRTY_SKIP_SCREENSHOT))))) {
             needApplyTransaction = true;
 
             if (wantSurfaceVisibleAndDrawn
@@ -260,6 +260,14 @@
                 t.setLayer(update.state.surfaceControl, surfaceLayer);
             }
 
+            if (wantSurfaceVisibleAndDrawn &&
+                (becomingVisible || (update.state.dirty & DIRTY_SKIP_SCREENSHOT))) {
+                int32_t flags =
+                        update.state.skipScreenshot ? ISurfaceComposerClient::eSkipScreenshot : 0;
+                t.setFlags(update.state.surfaceControl, flags,
+                           ISurfaceComposerClient::eSkipScreenshot);
+            }
+
             if (becomingVisible) {
                 t.show(update.state.surfaceControl);
 
@@ -333,19 +341,22 @@
 }
 
 sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height,
-                                                   int32_t displayId) {
+                                                   ui::LogicalDisplayId displayId,
+                                                   bool hideOnMirrored) {
     ensureSurfaceComposerClient();
 
     const sp<SurfaceControl> parent = mParentSurfaceProvider(displayId);
     if (parent == nullptr) {
-        ALOGE("Failed to get the parent surface for pointers on display %d", displayId);
+        LOG(ERROR) << "Failed to get the parent surface for pointers on display " << displayId;
     }
 
+    int32_t createFlags = ISurfaceComposerClient::eHidden | ISurfaceComposerClient::eCursorWindow;
+    if (hideOnMirrored) {
+        createFlags |= ISurfaceComposerClient::eSkipScreenshot;
+    }
     const sp<SurfaceControl> surfaceControl =
             mSurfaceComposerClient->createSurface(String8("Sprite"), width, height,
-                                                  PIXEL_FORMAT_RGBA_8888,
-                                                  ISurfaceComposerClient::eHidden |
-                                                          ISurfaceComposerClient::eCursorWindow,
+                                                  PIXEL_FORMAT_RGBA_8888, createFlags,
                                                   parent ? parent->getHandle() : nullptr);
     if (surfaceControl == nullptr || !surfaceControl->isValid()) {
         ALOGE("Error creating sprite surface.");
@@ -465,7 +476,7 @@
     }
 }
 
-void SpriteController::SpriteImpl::setDisplayId(int32_t displayId) {
+void SpriteController::SpriteImpl::setDisplayId(ui::LogicalDisplayId displayId) {
     AutoMutex _l(mController.mLock);
 
     if (mLocked.state.displayId != displayId) {
@@ -474,6 +485,15 @@
     }
 }
 
+void SpriteController::SpriteImpl::setSkipScreenshot(bool skip) {
+    AutoMutex _l(mController.mLock);
+
+    if (mLocked.state.skipScreenshot != skip) {
+        mLocked.state.skipScreenshot = skip;
+        invalidateLocked(DIRTY_SKIP_SCREENSHOT);
+    }
+}
+
 void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) {
     bool wasDirty = mLocked.state.dirty;
     mLocked.state.dirty |= dirty;
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index 35776e9..e147c56 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -95,7 +95,11 @@
     virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0;
 
     /* Sets the id of the display where the sprite should be shown. */
-    virtual void setDisplayId(int32_t displayId) = 0;
+    virtual void setDisplayId(ui::LogicalDisplayId displayId) = 0;
+
+    /* Sets the flag to hide sprite on mirrored displays.
+     * This will add ISurfaceComposerClient::eSkipScreenshot flag to the sprite. */
+    virtual void setSkipScreenshot(bool skip) = 0;
 };
 
 /*
@@ -111,7 +115,7 @@
  */
 class SpriteController {
 public:
-    using ParentSurfaceProvider = std::function<sp<SurfaceControl>(int /*displayId*/)>;
+    using ParentSurfaceProvider = std::function<sp<SurfaceControl>(ui::LogicalDisplayId)>;
     SpriteController(const sp<Looper>& looper, int32_t overlayLayer, ParentSurfaceProvider parent);
     SpriteController(const SpriteController&) = delete;
     SpriteController& operator=(const SpriteController&) = delete;
@@ -152,6 +156,7 @@
         DIRTY_DISPLAY_ID = 1 << 7,
         DIRTY_ICON_STYLE = 1 << 8,
         DIRTY_DRAW_DROP_SHADOW = 1 << 9,
+        DIRTY_SKIP_SCREENSHOT = 1 << 10,
     };
 
     /* Describes the state of a sprite.
@@ -160,28 +165,23 @@
      * on the sprites for a long time.
      * Note that the SpriteIcon holds a reference to a shared (and immutable) bitmap. */
     struct SpriteState {
-        inline SpriteState() :
-                dirty(0), visible(false),
-                positionX(0), positionY(0), layer(0), alpha(1.0f), displayId(ADISPLAY_ID_DEFAULT),
-                surfaceWidth(0), surfaceHeight(0), surfaceDrawn(false), surfaceVisible(false) {
-        }
-
-        uint32_t dirty;
+        uint32_t dirty{0};
 
         SpriteIcon icon;
-        bool visible;
-        float positionX;
-        float positionY;
-        int32_t layer;
-        float alpha;
+        bool visible{false};
+        float positionX{0};
+        float positionY{0};
+        int32_t layer{0};
+        float alpha{1.0f};
         SpriteTransformationMatrix transformationMatrix;
-        int32_t displayId;
+        ui::LogicalDisplayId displayId{ui::LogicalDisplayId::DEFAULT};
 
         sp<SurfaceControl> surfaceControl;
-        int32_t surfaceWidth;
-        int32_t surfaceHeight;
-        bool surfaceDrawn;
-        bool surfaceVisible;
+        int32_t surfaceWidth{0};
+        int32_t surfaceHeight{0};
+        bool surfaceDrawn{false};
+        bool surfaceVisible{false};
+        bool skipScreenshot{false};
 
         inline bool wantSurfaceVisible() const {
             return visible && alpha > 0.0f && icon.isValid();
@@ -208,7 +208,8 @@
         virtual void setLayer(int32_t layer);
         virtual void setAlpha(float alpha);
         virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix);
-        virtual void setDisplayId(int32_t displayId);
+        virtual void setDisplayId(ui::LogicalDisplayId displayId);
+        virtual void setSkipScreenshot(bool skip);
 
         inline const SpriteState& getStateLocked() const {
             return mLocked.state;
@@ -272,7 +273,8 @@
     void doDisposeSurfaces();
 
     void ensureSurfaceComposerClient();
-    sp<SurfaceControl> obtainSurface(int32_t width, int32_t height, int32_t displayId);
+    sp<SurfaceControl> obtainSurface(int32_t width, int32_t height, ui::LogicalDisplayId displayId,
+                                     bool hideOnMirrored);
 };
 
 } // namespace android
diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp
index 99952aa..7462481 100644
--- a/libs/input/TouchSpotController.cpp
+++ b/libs/input/TouchSpotController.cpp
@@ -40,12 +40,13 @@
 // --- Spot ---
 
 void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float newX, float newY,
-                                             int32_t displayId) {
+                                             ui::LogicalDisplayId displayId, bool skipScreenshot) {
     sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
     sprite->setAlpha(alpha);
     sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale));
     sprite->setPosition(newX, newY);
     sprite->setDisplayId(displayId);
+    sprite->setSkipScreenshot(skipScreenshot);
     x = newX;
     y = newY;
 
@@ -68,7 +69,8 @@
 
 // --- TouchSpotController ---
 
-TouchSpotController::TouchSpotController(int32_t displayId, PointerControllerContext& context)
+TouchSpotController::TouchSpotController(ui::LogicalDisplayId displayId,
+                                         PointerControllerContext& context)
       : mDisplayId(displayId), mContext(context) {
     mContext.getPolicy()->loadPointerResources(&mResources, mDisplayId);
 }
@@ -84,7 +86,7 @@
 }
 
 void TouchSpotController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
-                                   BitSet32 spotIdBits) {
+                                   BitSet32 spotIdBits, bool skipScreenshot) {
 #if DEBUG_SPOT_UPDATES
     ALOGD("setSpots: idBits=%08x", spotIdBits.value);
     for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) {
@@ -93,7 +95,7 @@
         const PointerCoords& c = spotCoords[spotIdToIndex[id]];
         ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f, displayId=%" PRId32 ".", id,
               c.getAxisValue(AMOTION_EVENT_AXIS_X), c.getAxisValue(AMOTION_EVENT_AXIS_Y),
-              c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), mDisplayId);
+              c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), mDisplayId.id);
     }
 #endif
 
@@ -116,7 +118,7 @@
             spot = createAndAddSpotLocked(id, mLocked.displaySpots);
         }
 
-        spot->updateSprite(&icon, x, y, mDisplayId);
+        spot->updateSprite(&icon, x, y, mDisplayId, skipScreenshot);
     }
 
     for (Spot* spot : mLocked.displaySpots) {
@@ -273,7 +275,7 @@
     out += prefix;
     out += "SpotController:\n";
     out += prefix;
-    StringAppendF(&out, INDENT "DisplayId: %" PRId32 "\n", mDisplayId);
+    StringAppendF(&out, INDENT "DisplayId: %s\n", mDisplayId.toString().c_str());
     std::scoped_lock lock(mLock);
     out += prefix;
     StringAppendF(&out, INDENT "Animating: %s\n", toString(mLocked.animating));
diff --git a/libs/input/TouchSpotController.h b/libs/input/TouchSpotController.h
index 5bbc75d..ac37fa4 100644
--- a/libs/input/TouchSpotController.h
+++ b/libs/input/TouchSpotController.h
@@ -29,10 +29,10 @@
  */
 class TouchSpotController {
 public:
-    TouchSpotController(int32_t displayId, PointerControllerContext& context);
+    TouchSpotController(ui::LogicalDisplayId displayId, PointerControllerContext& context);
     ~TouchSpotController();
     void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
-                  BitSet32 spotIdBits);
+                  BitSet32 spotIdBits, bool skipScreenshot);
     void clearSpots();
 
     void reloadSpotResources();
@@ -59,14 +59,15 @@
                 y(0.0f),
                 mLastIcon(nullptr) {}
 
-        void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId);
+        void updateSprite(const SpriteIcon* icon, float x, float y, ui::LogicalDisplayId displayId,
+                          bool skipScreenshot);
         void dump(std::string& out, const char* prefix = "") const;
 
     private:
         const SpriteIcon* mLastIcon;
     };
 
-    int32_t mDisplayId;
+    ui::LogicalDisplayId mDisplayId;
 
     mutable std::mutex mLock;
 
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index a1bb5b3..2dcb1f1 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include <com_android_input_flags.h>
 #include <flag_macros.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -30,8 +29,6 @@
 
 namespace android {
 
-namespace input_flags = com::android::input::flags;
-
 enum TestCursorType {
     CURSOR_TYPE_DEFAULT = 0,
     CURSOR_TYPE_HOVER,
@@ -55,20 +52,19 @@
 
 class MockPointerControllerPolicyInterface : public PointerControllerPolicyInterface {
 public:
-    virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) override;
-    virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) override;
+    virtual void loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId) override;
+    virtual void loadPointerResources(PointerResources* outResources,
+                                      ui::LogicalDisplayId displayId) override;
     virtual void loadAdditionalMouseResources(
             std::map<PointerIconStyle, SpriteIcon>* outResources,
             std::map<PointerIconStyle, PointerAnimation>* outAnimationResources,
-            int32_t displayId) override;
+            ui::LogicalDisplayId displayId) override;
     virtual PointerIconStyle getDefaultPointerIconId() override;
     virtual PointerIconStyle getDefaultStylusIconId() override;
     virtual PointerIconStyle getCustomPointerIconId() override;
-    virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) override;
 
     bool allResourcesAreLoaded();
     bool noResourcesAreLoaded();
-    std::optional<int32_t> getLastReportedPointerDisplayId() { return latestPointerDisplayId; }
 
 private:
     void loadPointerIconForType(SpriteIcon* icon, int32_t cursorType);
@@ -76,16 +72,15 @@
     bool pointerIconLoaded{false};
     bool pointerResourcesLoaded{false};
     bool additionalMouseResourcesLoaded{false};
-    std::optional<int32_t /*displayId*/> latestPointerDisplayId;
 };
 
-void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, int32_t) {
+void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId) {
     loadPointerIconForType(icon, CURSOR_TYPE_DEFAULT);
     pointerIconLoaded = true;
 }
 
 void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources* outResources,
-        int32_t) {
+                                                                ui::LogicalDisplayId) {
     loadPointerIconForType(&outResources->spotHover, CURSOR_TYPE_HOVER);
     loadPointerIconForType(&outResources->spotTouch, CURSOR_TYPE_TOUCH);
     loadPointerIconForType(&outResources->spotAnchor, CURSOR_TYPE_ANCHOR);
@@ -94,7 +89,7 @@
 
 void MockPointerControllerPolicyInterface::loadAdditionalMouseResources(
         std::map<PointerIconStyle, SpriteIcon>* outResources,
-        std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t) {
+        std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, ui::LogicalDisplayId) {
     SpriteIcon icon;
     PointerAnimation anim;
 
@@ -146,12 +141,6 @@
     icon->hotSpotY = hotSpot.second;
 }
 
-void MockPointerControllerPolicyInterface::onPointerDisplayIdChanged(int32_t displayId,
-                                                                     const FloatPoint& /*position*/
-) {
-    latestPointerDisplayId = displayId;
-}
-
 class TestPointerController : public PointerController {
 public:
     TestPointerController(sp<android::gui::WindowInfosListener>& registeredListener,
@@ -159,7 +148,6 @@
                           SpriteController& spriteController)
           : PointerController(
                     policy, looper, spriteController,
-                    /*enabled=*/true,
                     [&registeredListener](const sp<android::gui::WindowInfosListener>& listener)
                             -> std::vector<gui::DisplayInfo> {
                         // Register listener
@@ -178,7 +166,7 @@
     PointerControllerTest();
     ~PointerControllerTest();
 
-    void ensureDisplayViewportIsSet(int32_t displayId = ADISPLAY_ID_DEFAULT);
+    void ensureDisplayViewportIsSet(ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT);
 
     sp<MockSprite> mPointerSprite;
     sp<MockPointerControllerPolicyInterface> mPolicy;
@@ -217,7 +205,7 @@
     mThread.join();
 }
 
-void PointerControllerTest::ensureDisplayViewportIsSet(int32_t displayId) {
+void PointerControllerTest::ensureDisplayViewportIsSet(ui::LogicalDisplayId displayId) {
     DisplayViewport viewport;
     viewport.displayId = displayId;
     viewport.logicalRight = 1600;
@@ -267,8 +255,7 @@
     mPointerController->reloadPointerResources();
 }
 
-TEST_F_WITH_FLAGS(PointerControllerTest, setPresentationBeforeDisplayViewportDoesNotLoadResources,
-                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(input_flags, enable_pointer_choreographer))) {
+TEST_F(PointerControllerTest, setPresentationBeforeDisplayViewportDoesNotLoadResources) {
     // Setting the presentation mode before a display viewport is set will not load any resources.
     mPointerController->setPresentation(PointerController::Presentation::POINTER);
     ASSERT_TRUE(mPolicy->noResourcesAreLoaded());
@@ -278,26 +265,7 @@
     ASSERT_TRUE(mPolicy->allResourcesAreLoaded());
 }
 
-TEST_F_WITH_FLAGS(PointerControllerTest, updatePointerIcon,
-                  REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(input_flags,
-                                                       enable_pointer_choreographer))) {
-    ensureDisplayViewportIsSet();
-    mPointerController->setPresentation(PointerController::Presentation::POINTER);
-    mPointerController->unfade(PointerController::Transition::IMMEDIATE);
-
-    int32_t type = CURSOR_TYPE_ADDITIONAL;
-    std::pair<float, float> hotspot = getHotSpotCoordinatesForType(type);
-    EXPECT_CALL(*mPointerSprite, setVisible(true));
-    EXPECT_CALL(*mPointerSprite, setAlpha(1.0f));
-    EXPECT_CALL(*mPointerSprite,
-                setIcon(AllOf(Field(&SpriteIcon::style, static_cast<PointerIconStyle>(type)),
-                              Field(&SpriteIcon::hotSpotX, hotspot.first),
-                              Field(&SpriteIcon::hotSpotY, hotspot.second))));
-    mPointerController->updatePointerIcon(static_cast<PointerIconStyle>(type));
-}
-
-TEST_F_WITH_FLAGS(PointerControllerTest, updatePointerIconWithChoreographer,
-                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(input_flags, enable_pointer_choreographer))) {
+TEST_F(PointerControllerTest, updatePointerIconWithChoreographer) {
     // When PointerChoreographer is enabled, the presentation mode is set before the viewport.
     mPointerController->setPresentation(PointerController::Presentation::POINTER);
     ensureDisplayViewportIsSet();
@@ -348,28 +316,43 @@
     ensureDisplayViewportIsSet();
 }
 
-TEST_F(PointerControllerTest, notifiesPolicyWhenPointerDisplayChanges) {
-    EXPECT_FALSE(mPolicy->getLastReportedPointerDisplayId())
-            << "A pointer display change does not occur when PointerController is created.";
+TEST_F(PointerControllerTest, updatesSkipScreenshotFlagForTouchSpots) {
+    ensureDisplayViewportIsSet();
 
-    ensureDisplayViewportIsSet(ADISPLAY_ID_DEFAULT);
+    PointerCoords testSpotCoords;
+    testSpotCoords.clear();
+    testSpotCoords.setAxisValue(AMOTION_EVENT_AXIS_X, 1);
+    testSpotCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, 1);
+    BitSet32 testIdBits;
+    testIdBits.markBit(0);
+    std::array<uint32_t, MAX_POINTER_ID + 1> testIdToIndex;
 
-    const auto lastReportedPointerDisplayId = mPolicy->getLastReportedPointerDisplayId();
-    ASSERT_TRUE(lastReportedPointerDisplayId)
-            << "The policy is notified of a pointer display change when the viewport is first set.";
-    EXPECT_EQ(ADISPLAY_ID_DEFAULT, *lastReportedPointerDisplayId)
-            << "Incorrect pointer display notified.";
+    sp<MockSprite> testSpotSprite(new NiceMock<MockSprite>);
 
-    ensureDisplayViewportIsSet(42);
+    // By default sprite is not marked secure
+    EXPECT_CALL(*mSpriteController, createSprite).WillOnce(Return(testSpotSprite));
+    EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false));
 
-    EXPECT_EQ(42, *mPolicy->getLastReportedPointerDisplayId())
-            << "The policy is notified when the pointer display changes.";
+    // Update spots to sync state with sprite
+    mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
+                                 ui::LogicalDisplayId::DEFAULT);
+    testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
 
-    // Release the PointerController.
-    mPointerController = nullptr;
+    // Marking the display to skip screenshot should update sprite as well
+    mPointerController->setSkipScreenshot(ui::LogicalDisplayId::DEFAULT, true);
+    EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(true));
 
-    EXPECT_EQ(ADISPLAY_ID_NONE, *mPolicy->getLastReportedPointerDisplayId())
-            << "The pointer display changes to invalid when PointerController is destroyed.";
+    // Update spots to sync state with sprite
+    mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
+                                 ui::LogicalDisplayId::DEFAULT);
+    testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
+
+    // Reset flag and verify again
+    mPointerController->setSkipScreenshot(ui::LogicalDisplayId::DEFAULT, false);
+    EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false));
+    mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
+                                 ui::LogicalDisplayId::DEFAULT);
+    testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
 }
 
 class PointerControllerWindowInfoListenerTest : public Test {};
diff --git a/libs/input/tests/mocks/MockSprite.h b/libs/input/tests/mocks/MockSprite.h
index 013b79c..21628fb 100644
--- a/libs/input/tests/mocks/MockSprite.h
+++ b/libs/input/tests/mocks/MockSprite.h
@@ -33,7 +33,8 @@
     MOCK_METHOD(void, setLayer, (int32_t), (override));
     MOCK_METHOD(void, setAlpha, (float), (override));
     MOCK_METHOD(void, setTransformationMatrix, (const SpriteTransformationMatrix&), (override));
-    MOCK_METHOD(void, setDisplayId, (int32_t), (override));
+    MOCK_METHOD(void, setDisplayId, (ui::LogicalDisplayId), (override));
+    MOCK_METHOD(void, setSkipScreenshot, (bool), (override));
 };
 
 }  // namespace android
diff --git a/libs/input/tests/mocks/MockSpriteController.h b/libs/input/tests/mocks/MockSpriteController.h
index 62f1d65..9ef6b7c 100644
--- a/libs/input/tests/mocks/MockSpriteController.h
+++ b/libs/input/tests/mocks/MockSpriteController.h
@@ -27,7 +27,7 @@
 
 public:
     MockSpriteController(sp<Looper> looper)
-          : SpriteController(looper, 0, [](int) { return nullptr; }) {}
+          : SpriteController(looper, 0, [](ui::LogicalDisplayId) { return nullptr; }) {}
     ~MockSpriteController() {}
 
     MOCK_METHOD(sp<Sprite>, createSprite, (), (override));
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 554fe5e..7ddf11e 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -50,6 +50,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.media.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -984,37 +985,7 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void transfer(@NonNull RoutingController controller, @NonNull MediaRoute2Info route) {
-        mImpl.transfer(
-                controller.getRoutingSessionInfo(),
-                route,
-                Process.myUserHandle(),
-                mContext.getPackageName());
-    }
-
-    /**
-     * Transfers the media of a routing controller to the given route.
-     *
-     * <p>This will be no-op for non-system media routers.
-     *
-     * @param controller a routing controller controlling media routing.
-     * @param route the route you want to transfer the media to.
-     * @param transferInitiatorUserHandle the user handle of the app that initiated the transfer
-     *     request.
-     * @param transferInitiatorPackageName the package name of the app that initiated the transfer.
-     *     This value is used with the user handle to populate {@link
-     *     RoutingController#wasTransferInitiatedBySelf()}.
-     * @hide
-     */
-    public void transfer(
-            @NonNull RoutingController controller,
-            @NonNull MediaRoute2Info route,
-            @NonNull UserHandle transferInitiatorUserHandle,
-            @NonNull String transferInitiatorPackageName) {
-        mImpl.transfer(
-                controller.getRoutingSessionInfo(),
-                route,
-                transferInitiatorUserHandle,
-                transferInitiatorPackageName);
+        mImpl.transfer(controller.getRoutingSessionInfo(), route);
     }
 
     void requestCreateController(
@@ -1913,13 +1884,7 @@
          */
         @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
         public boolean wasTransferInitiatedBySelf() {
-            RoutingSessionInfo sessionInfo = getRoutingSessionInfo();
-
-            UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle();
-            String transferInitiatorPackageName = sessionInfo.getTransferInitiatorPackageName();
-
-            return Objects.equals(Process.myUserHandle(), transferInitiatorUserHandle)
-                    && Objects.equals(mContext.getPackageName(), transferInitiatorPackageName);
+            return mImpl.wasTransferredBySelf(getRoutingSessionInfo());
         }
 
         /**
@@ -2064,24 +2029,47 @@
         }
 
         /**
-         * Transfers to a given route for the remote session. The given route must be included in
-         * {@link RoutingSessionInfo#getTransferableRoutes()}.
+         * Attempts a transfer to a {@link RoutingSessionInfo#getTransferableRoutes() transferable
+         * route}.
          *
+         * <p>Transferring to a transferable route does not require the app to transfer the playback
+         * state from one route to the other. The route provider completely manages the transfer. An
+         * example of provider-managed transfers are the switches between the system's routes, like
+         * the built-in speakers and a BT headset.
+         *
+         * @return True if the transfer is handled by this controller, or false if a new controller
+         *     should be created instead.
          * @see RoutingSessionInfo#getSelectedRoutes()
          * @see RoutingSessionInfo#getTransferableRoutes()
          * @see ControllerCallback#onControllerUpdated
          */
-        void transferToRoute(@NonNull MediaRoute2Info route) {
+        boolean tryTransferWithinProvider(@NonNull MediaRoute2Info route) {
             Objects.requireNonNull(route, "route must not be null");
             synchronized (mControllerLock) {
                 if (isReleased()) {
-                    Log.w(TAG, "transferToRoute: Called on released controller. Ignoring.");
-                    return;
+                    Log.w(
+                            TAG,
+                            "tryTransferWithinProvider: Called on released controller. Ignoring.");
+                    return true;
                 }
 
-                if (!mSessionInfo.getTransferableRoutes().contains(route.getId())) {
-                    Log.w(TAG, "Ignoring transferring to a non-transferable route=" + route);
-                    return;
+                // If this call is trying to transfer to a selected system route, we let them
+                // through as a provider driven transfer in order to update the transfer reason and
+                // initiator data.
+                boolean isSystemRouteReselection =
+                        Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()
+                                && mSessionInfo.isSystemSession()
+                                && route.isSystemRoute()
+                                && mSessionInfo.getSelectedRoutes().contains(route.getId());
+                if (!isSystemRouteReselection
+                        && !mSessionInfo.getTransferableRoutes().contains(route.getId())) {
+                    Log.i(
+                            TAG,
+                            "Transferring to a non-transferable route="
+                                    + route
+                                    + " session= "
+                                    + mSessionInfo.getId());
+                    return false;
                 }
             }
 
@@ -2096,6 +2084,7 @@
                     Log.e(TAG, "Unable to transfer to route for session.", ex);
                 }
             }
+            return true;
         }
 
         /**
@@ -2490,11 +2479,7 @@
 
         void stop();
 
-        void transfer(
-                @NonNull RoutingSessionInfo sessionInfo,
-                @NonNull MediaRoute2Info route,
-                @NonNull UserHandle transferInitiatorUserHandle,
-                @NonNull String transferInitiatorPackageName);
+        void transfer(@NonNull RoutingSessionInfo sessionInfo, @NonNull MediaRoute2Info route);
 
         List<RoutingController> getControllers();
 
@@ -2515,6 +2500,11 @@
                 boolean shouldNotifyStop,
                 RoutingController controller);
 
+        /**
+         * Returns the value of {@link RoutingController#wasTransferInitiatedBySelf()} for the app
+         * associated with this router.
+         */
+        boolean wasTransferredBySelf(RoutingSessionInfo sessionInfo);
     }
 
     /**
@@ -2715,7 +2705,7 @@
 
             List<RoutingSessionInfo> sessionInfos = getRoutingSessions();
             RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1);
-            transfer(targetSession, route, mClientUser, mContext.getPackageName());
+            transfer(targetSession, route);
         }
 
         @Override
@@ -2738,24 +2728,15 @@
          *
          * @param sessionInfo The {@link RoutingSessionInfo routing session} to transfer.
          * @param route The {@link MediaRoute2Info route} to transfer to.
-         * @param transferInitiatorUserHandle The user handle of the app that initiated the
-         *     transfer.
-         * @param transferInitiatorPackageName The package name if of the app that initiated the
-         *     transfer.
          * @see #transferToRoute(RoutingSessionInfo, MediaRoute2Info, UserHandle, String)
          * @see #requestCreateSession(RoutingSessionInfo, MediaRoute2Info)
          */
         @Override
         @SuppressWarnings("AndroidFrameworkRequiresPermission")
         public void transfer(
-                @NonNull RoutingSessionInfo sessionInfo,
-                @NonNull MediaRoute2Info route,
-                @NonNull UserHandle transferInitiatorUserHandle,
-                @NonNull String transferInitiatorPackageName) {
+                @NonNull RoutingSessionInfo sessionInfo, @NonNull MediaRoute2Info route) {
             Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
             Objects.requireNonNull(route, "route must not be null");
-            Objects.requireNonNull(transferInitiatorUserHandle);
-            Objects.requireNonNull(transferInitiatorPackageName);
 
             Log.v(
                     TAG,
@@ -2772,15 +2753,19 @@
                 return;
             }
 
-            if (sessionInfo.getTransferableRoutes().contains(route.getId())) {
-                transferToRoute(
-                        sessionInfo,
-                        route,
-                        transferInitiatorUserHandle,
-                        transferInitiatorPackageName);
+            // If this call is trying to transfer to a selected system route, we let them
+            // through as a provider driven transfer in order to update the transfer reason and
+            // initiator data.
+            boolean isSystemRouteReselection =
+                    Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()
+                            && sessionInfo.isSystemSession()
+                            && route.isSystemRoute()
+                            && sessionInfo.getSelectedRoutes().contains(route.getId());
+            if (sessionInfo.getTransferableRoutes().contains(route.getId())
+                    || isSystemRouteReselection) {
+                transferToRoute(sessionInfo, route, mClientUser, mClientPackageName);
             } else {
-                requestCreateSession(sessionInfo, route, transferInitiatorUserHandle,
-                        transferInitiatorPackageName);
+                requestCreateSession(sessionInfo, route, mClientUser, mClientPackageName);
             }
         }
 
@@ -3035,6 +3020,14 @@
             releaseSession(controller.getRoutingSessionInfo());
         }
 
+        @Override
+        public boolean wasTransferredBySelf(RoutingSessionInfo sessionInfo) {
+            UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle();
+            String transferInitiatorPackageName = sessionInfo.getTransferInitiatorPackageName();
+            return Objects.equals(mClientUser, transferInitiatorUserHandle)
+                    && Objects.equals(mClientPackageName, transferInitiatorPackageName);
+        }
+
         /**
          * Retrieves the system session info for the given package.
          *
@@ -3587,20 +3580,14 @@
             }
 
             RoutingController controller = getCurrentController();
-            if (controller
-                    .getRoutingSessionInfo()
-                    .getTransferableRoutes()
-                    .contains(route.getId())) {
-                controller.transferToRoute(route);
-                return;
+            if (!controller.tryTransferWithinProvider(route)) {
+                requestCreateController(
+                        controller,
+                        route,
+                        MANAGER_REQUEST_ID_NONE,
+                        Process.myUserHandle(),
+                        mContext.getPackageName());
             }
-
-            requestCreateController(
-                    controller,
-                    route,
-                    MANAGER_REQUEST_ID_NONE,
-                    Process.myUserHandle(),
-                    mContext.getPackageName());
         }
 
         @Override
@@ -3617,10 +3604,7 @@
          */
         @Override
         public void transfer(
-                @NonNull RoutingSessionInfo sessionInfo,
-                @NonNull MediaRoute2Info route,
-                @NonNull UserHandle transferInitiatorUserHandle,
-                @NonNull String transferInitiatorPackageName) {
+                @NonNull RoutingSessionInfo sessionInfo, @NonNull MediaRoute2Info route) {
             // Do nothing.
         }
 
@@ -3739,6 +3723,14 @@
             }
         }
 
+        @Override
+        public boolean wasTransferredBySelf(RoutingSessionInfo sessionInfo) {
+            UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle();
+            String transferInitiatorPackageName = sessionInfo.getTransferInitiatorPackageName();
+            return Objects.equals(Process.myUserHandle(), transferInitiatorUserHandle)
+                    && Objects.equals(mContext.getPackageName(), transferInitiatorPackageName);
+        }
+
         @GuardedBy("mLock")
         private void registerRouterStubIfNeededLocked() throws RemoteException {
             if (mStub == null) {
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index b43ff63..a488756 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -252,18 +252,14 @@
         return 0;
     }
 
-    /**
-     * Get the current playback info for this session.
-     *
-     * @return The current playback info or null.
-     */
-    public @Nullable PlaybackInfo getPlaybackInfo() {
+    /** Returns the current playback info for this session. */
+    @NonNull
+    public PlaybackInfo getPlaybackInfo() {
         try {
             return mSessionBinder.getVolumeAttributes();
-        } catch (RemoteException e) {
-            Log.wtf(TAG, "Error calling getAudioInfo.", e);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
-        return null;
     }
 
     /**
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 58f56b8..78a5357 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -16,6 +16,7 @@
 
 #define LOG_TAG "PerformanceHintNativeTest"
 
+#include <aidl/android/hardware/power/ChannelConfig.h>
 #include <aidl/android/hardware/power/SessionConfig.h>
 #include <aidl/android/hardware/power/SessionTag.h>
 #include <aidl/android/hardware/power/WorkDuration.h>
@@ -54,6 +55,9 @@
     MOCK_METHOD(ScopedAStatus, getHintSessionThreadIds,
                 (const std::shared_ptr<IHintSession>& hintSession, ::std::vector<int32_t>* tids),
                 (override));
+    MOCK_METHOD(ScopedAStatus, getSessionChannel,
+                (const ::ndk::SpAIBinder& in_token, hal::ChannelConfig* _aidl_return), (override));
+    MOCK_METHOD(ScopedAStatus, closeSessionChannel, (), (override));
     MOCK_METHOD(SpAIBinder, asBinder, (), (override));
     MOCK_METHOD(bool, isRemote, (), (override));
 };
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 6d4cc3a..cf7aea4 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -64,8 +64,10 @@
   }
 
   public final class NfcAdapter {
+    method @FlaggedApi("android.nfc.nfc_state_change") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable();
     method public void disableForegroundDispatch(android.app.Activity);
     method public void disableReaderMode(android.app.Activity);
+    method @FlaggedApi("android.nfc.nfc_state_change") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
     method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]);
     method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
     method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 310130e..a33e225 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -3,9 +3,7 @@
 
   public final class NfcAdapter {
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean addNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler, String[]);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable();
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(boolean);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
     method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getAdapterState();
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index e43d104..06098de 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -1106,6 +1106,9 @@
      * {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the
      * operation is complete.
      *
+     * <p>This API is only allowed to be called by system apps
+     * or apps which are Device Owner or Profile Owner.
+     *
      * <p>If this returns true, then either NFC is already on, or
      * a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent
      * to indicate a state transition. If this returns false, then
@@ -1113,9 +1116,8 @@
      * NFC on (for example we are in airplane mode and NFC is not
      * toggleable in airplane mode on this platform).
      *
-     * @hide
      */
-    @SystemApi
+    @FlaggedApi(Flags.FLAG_NFC_STATE_CHANGE)
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     public boolean enable() {
         try {
@@ -1146,15 +1148,17 @@
      * {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the
      * operation is complete.
      *
+     * <p>This API is only allowed to be called by system apps
+     * or apps which are Device Owner or Profile Owner.
+     *
      * <p>If this returns true, then either NFC is already off, or
      * a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent
      * to indicate a state transition. If this returns false, then
      * there is some problem that prevents an attempt to turn
      * NFC off.
      *
-     * @hide
      */
-    @SystemApi
+    @FlaggedApi(Flags.FLAG_NFC_STATE_CHANGE)
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     public boolean disable() {
         try {
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index de9eada1..2fe2ce3 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -1212,16 +1212,16 @@
      *
      * @param service The ComponentName of the service
      * @param status  true to enable, false to disable
+     * @param userId the user handle of the user whose information is being requested.
      * @return set service for the category and true if service is already set return false.
      *
      * @hide
      */
-    public boolean setServiceEnabledForCategoryOther(ComponentName service, boolean status) {
+    public boolean setServiceEnabledForCategoryOther(ComponentName service, boolean status,
+                                                     int userId) {
         if (service == null) {
             throw new NullPointerException("activity or service or category is null");
         }
-        int userId = mContext.getUser().getIdentifier();
-
         try {
             return sService.setServiceEnabledForCategoryOther(userId, service, status);
         } catch (RemoteException e) {
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index f674b06a..c3c74a6 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
 import android.app.Service;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -404,6 +405,7 @@
      *
      * @param frame A description of the polling frame.
      */
+    @SuppressLint("OnNameExpected")
     @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
     public void processPollingFrames(@NonNull List<PollingFrame> frame) {
     }
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index 73b29db..cb2a48c 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -93,3 +93,11 @@
     description: "Enable NFC OEM extension support"
     bug: "331206243"
 }
+
+flag {
+    name: "nfc_state_change"
+    is_exported: true
+    namespace: "nfc"
+    description: "Enable nfc state change API"
+    bug: "319934052"
+}
diff --git a/packages/CarrierDefaultApp/res/values-fa/strings.xml b/packages/CarrierDefaultApp/res/values-fa/strings.xml
index abf47fb..d9afe873 100644
--- a/packages/CarrierDefaultApp/res/values-fa/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-fa/strings.xml
@@ -17,7 +17,7 @@
     <string name="performance_boost_notification_channel" msgid="3475440855635538592">"تقویت‌کننده عملکرد"</string>
     <string name="performance_boost_notification_title" msgid="3126203390685781861">"گزینه‌های نسل پنجم شرکت مخابراتی شما"</string>
     <string name="performance_boost_notification_detail" msgid="216569851036236346">"‏برای دیدن گزینه‌های مرتبط با تجربه برنامه، به وب‌سایت %s مراجعه کنید"</string>
-    <string name="performance_boost_notification_button_not_now" msgid="6459755324243683785">"اکنون نه"</string>
+    <string name="performance_boost_notification_button_not_now" msgid="6459755324243683785">"حالا نه"</string>
     <string name="performance_boost_notification_button_manage" msgid="4976836444046497973">"مدیریت"</string>
     <string name="slice_purchase_app_label" msgid="7170191659233241166">"تقویت‌کننده عملکرد خریداری کنید."</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-fr/strings.xml b/packages/CompanionDeviceManager/res/values-fr/strings.xml
index a33e0dc2..7015c50 100644
--- a/packages/CompanionDeviceManager/res/values-fr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fr/strings.xml
@@ -38,7 +38,7 @@
     <string name="helper_title_computer" msgid="4671071173916176037">"Services Google Play"</string>
     <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> pour accéder aux photos, contenus multimédias et notifications de votre téléphone"</string>
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Autoriser &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; à effectuer cette action ?"</string>
-    <string name="title_nearby_device_streaming_with_mirroring" msgid="242855799919611657">"Autoriser &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; à caster les applications et les fonctionnalités système de votre téléphone ?"</string>
+    <string name="title_nearby_device_streaming_with_mirroring" msgid="242855799919611657">"Autoriser &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; à streamer les applications et les fonctionnalités système de votre téléphone ?"</string>
     <string name="summary_nearby_device_streaming" msgid="4039565463149145573">"%1$s aura accès à tout ce qui est visible ou lu sur le téléphone, y compris les contenus audio, les photos, les informations de paiement, les mots de passe et les messages.&lt;br/&gt;&lt;br/&gt;%1$s pourra caster des applications et des fonctionnalités système jusqu\'à ce que vous supprimiez l\'accès à cette autorisation."</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DEVICE_NAME">%2$s</xliff:g> de diffuser des applis et d\'autres fonctionnalités système en streaming sur des appareils à proximité"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"appareil"</string>
diff --git a/packages/CompanionDeviceManager/res/values-hu/strings.xml b/packages/CompanionDeviceManager/res/values-hu/strings.xml
index 4985ae3c..c929ee4 100644
--- a/packages/CompanionDeviceManager/res/values-hu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hu/strings.xml
@@ -54,7 +54,7 @@
     <string name="vendor_header_button_description" msgid="7994879208461111473">"További információ"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
-    <string name="permission_contacts" msgid="3858319347208004438">"Címtár"</string>
+    <string name="permission_contacts" msgid="3858319347208004438">"Névjegyek"</string>
     <string name="permission_calendar" msgid="6805668388691290395">"Naptár"</string>
     <string name="permission_microphone" msgid="2152206421428732949">"Mikrofon"</string>
     <string name="permission_call_logs" msgid="5546761417694586041">"Hívásnaplók"</string>
diff --git a/packages/CredentialManager/res/values-es-rUS/strings.xml b/packages/CredentialManager/res/values-es-rUS/strings.xml
index ef27359..f749909 100644
--- a/packages/CredentialManager/res/values-es-rUS/strings.xml
+++ b/packages/CredentialManager/res/values-es-rUS/strings.xml
@@ -91,7 +91,7 @@
     <string name="no_sign_in_info_in" msgid="2641118151920288356">"No hay información de acceso en <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Administrar accesos"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Desde otro dispositivo"</string>
-    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar otra voz"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar otro dispositivo"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> canceló la solicitud"</string>
     <string name="dropdown_presentation_more_sign_in_options_text" msgid="1693727354272417902">"Opciones de acceso"</string>
     <string name="more_options_content_description" msgid="1323427365788198808">"Más"</string>
diff --git a/packages/CredentialManager/res/values-fr/strings.xml b/packages/CredentialManager/res/values-fr/strings.xml
index b239fc3..8617c6a 100644
--- a/packages/CredentialManager/res/values-fr/strings.xml
+++ b/packages/CredentialManager/res/values-fr/strings.xml
@@ -20,7 +20,7 @@
     <string name="app_name" msgid="4539824758261855508">"Gestionnaire d\'identifiants"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Annuler"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continuer"</string>
-    <string name="string_more_options" msgid="2763852250269945472">"Autre façon"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Autre option"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"En savoir plus"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Afficher le mot de passe"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Masquer le mot de passe"</string>
@@ -40,7 +40,7 @@
     <string name="choose_provider_title" msgid="8870795677024868108">"Choisissez où enregistrer vos <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Sélectionnez un gestionnaire de mots de passe pour enregistrer vos informations et vous connecter plus rapidement la prochaine fois"</string>
     <string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Créer une clé d\'accès pour se connecter à <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
-    <string name="choose_create_option_password_title" msgid="4481366993598649224">"Enregistrer un mot de passe pour se connecter à <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
+    <string name="choose_create_option_password_title" msgid="4481366993598649224">"Enregistrer le mot de passe pour se connecter à <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
     <string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Enregistrer les informations de connexion pour <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
     <string name="passkey" msgid="632353688396759522">"clé d\'accès"</string>
     <string name="password" msgid="6738570945182936667">"mot de passe"</string>
diff --git a/packages/CredentialManager/res/values-hy/strings.xml b/packages/CredentialManager/res/values-hy/strings.xml
index 868357f..3ec7da3 100644
--- a/packages/CredentialManager/res/values-hy/strings.xml
+++ b/packages/CredentialManager/res/values-hy/strings.xml
@@ -28,7 +28,7 @@
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Անցաբառերի շնորհիվ դուք բարդ գաղտնաբառեր ստեղծելու կամ հիշելու անհրաժեշտություն չեք ունենա"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Անցաբառերը գաղտնագրված թվային բանալիներ են, որոնք ստեղծվում են մատնահետքի, դեմքով ապակողպման կամ էկրանի կողպման օգտագործմամբ"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Դուք կարող եք մուտք գործել այլ սարքերում, քանի որ մուտքի բանալիները պահվում են գաղտնաբառերի կառավարիչում"</string>
-    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Ավելին՝ անցաբառերի մասին"</string>
+    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Ավելին՝ մուտքի բանալիների մասին"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Գաղտնաբառեր չպահանջող տեխնոլոգիա"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"Մուտքի բանալիները ձեզ թույլ են տալիս մուտք գործել առանց գաղտնաբառերի։ Ձեզ պարզապես հարկավոր է օգտագործել ձեր մատնահետքը, դիմաճանաչումը, PIN կոդը կամ նախշը՝ ձեր ինքնությունը հաստատելու և մուտքի բանալի ստեղծելու համար։"</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Բաց բանալու կրիպտոգրաֆիա"</string>
@@ -36,7 +36,7 @@
     <string name="improved_account_security_title" msgid="1069841917893513424">"Հաշվի բարելավված անվտանգություն"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Յուրաքանչյուր բանալի բացառապես կապված է հավելվածի կամ կայքի հետ, որի համար այն ստեղծվել է, ուստի դուք երբեք չեք կարող սխալմամբ մուտք գործել կեղծ հավելված կամ կայք։ Բացի այդ՝ սերվերներում պահվում են միայն բաց բանալիներ, ինչը զգալիորեն դժվարացնում է կոտրումը։"</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Սահուն անցում"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"Թեև մենք առանց գաղտնաբառերի ապագայի ճանապարհին ենք, դրանք դեռ հասանելի կլինեն անցաբառերի հետ մեկտեղ։"</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"Թեև մենք առանց գաղտնաբառերի ապագայի ճանապարհին ենք, դրանք դեռ հասանելի կլինեն մուտքի բանալիների հետ մեկտեղ։"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Նշեք, թե որտեղ եք ուզում պահել ձեր <xliff:g id="CREATETYPES">%1$s</xliff:g>ը"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Ընտրեք գաղտնաբառերի կառավարիչ՝ ձեր տեղեկությունները պահելու և հաջորդ անգամ ավելի արագ մուտք գործելու համար"</string>
     <string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Ստեղծե՞լ մուտքի բանալի՝ <xliff:g id="APP_NAME">%1$s</xliff:g> հավելված մուտք գործելու համար"</string>
@@ -44,7 +44,7 @@
     <string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Պահե՞լ «<xliff:g id="APP_NAME">%1$s</xliff:g>» հավելվածի մուտքի տվյալները"</string>
     <string name="passkey" msgid="632353688396759522">"մուտքի բանալի"</string>
     <string name="password" msgid="6738570945182936667">"գաղտնաբառ"</string>
-    <string name="passkeys" msgid="5733880786866559847">"անցաբառեր"</string>
+    <string name="passkeys" msgid="5733880786866559847">"մուտքի բանալիներ"</string>
     <string name="passwords" msgid="5419394230391253816">"գաղտնաբառեր"</string>
     <string name="sign_ins" msgid="4710739369149469208">"մուտք"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"մուտքի տվյալներ"</string>
diff --git a/packages/CredentialManager/res/values-or/strings.xml b/packages/CredentialManager/res/values-or/strings.xml
index e3a9191..9885a1f 100644
--- a/packages/CredentialManager/res/values-or/strings.xml
+++ b/packages/CredentialManager/res/values-or/strings.xml
@@ -57,9 +57,9 @@
     <string name="set_as_default" msgid="4415328591568654603">"ଡିଫଲ୍ଟ ଭାବେ ସେଟ କରନ୍ତୁ"</string>
     <string name="settings" msgid="6536394145760913145">"ସେଟିଂସ"</string>
     <string name="use_once" msgid="9027366575315399714">"ଥରେ ବ୍ୟବହାର କରନ୍ତୁ"</string>
-    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ଟି ପାସୱାର୍ଡ • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>ଟି ପାସକୀ"</string>
-    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ଟି ପାସୱାର୍ଡ"</string>
-    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>ଟି ପାସକୀ"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ପାସୱାର୍ଡ • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ପାସକୀ"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ପାସୱାର୍ଡ"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> ପାସକୀ"</string>
     <string name="more_options_usage_credentials" msgid="1785697001787193984">"<xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g>ଟି କ୍ରେଡେନସିଆଲ"</string>
     <string name="passkey_before_subtitle" msgid="2448119456208647444">"ପାସକୀ"</string>
     <string name="another_device" msgid="5147276802037801217">"ଅନ୍ୟ ଏକ ଡିଭାଇସ"</string>
diff --git a/packages/CredentialManager/res/values-zh-rHK/strings.xml b/packages/CredentialManager/res/values-zh-rHK/strings.xml
index 44f5eaa..c6ac743 100644
--- a/packages/CredentialManager/res/values-zh-rHK/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rHK/strings.xml
@@ -74,7 +74,7 @@
     <string name="get_dialog_description_single_tap" msgid="2797059565126030879">"使用螢幕鎖定方式以 <xliff:g id="USERNAME">%2$s</xliff:g> 登入 <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_title_unlock_options_for" msgid="7096423827682163270">"解鎖 <xliff:g id="APP_NAME">%1$s</xliff:g> 的登入選項"</string>
     <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」密鑰"</string>
-    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」密碼"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"選擇「<xliff:g id="APP_NAME">%1$s</xliff:g>」的儲存密碼"</string>
     <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」登入資料"</string>
     <string name="get_dialog_title_choose_sign_in_for" msgid="645728947702442421">"選擇使用 <xliff:g id="APP_NAME">%1$s</xliff:g> 的帳戶"</string>
     <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"要選擇適用於「<xliff:g id="APP_NAME">%1$s</xliff:g>」的項目嗎?"</string>
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 46a5138..0bae63a 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -24,7 +24,7 @@
   <string name="string_cancel">Cancel</string>
   <!-- This is a label for a button that takes user to the next screen. [CHAR LIMIT=20] -->
   <string name="string_continue">Continue</string>
-  <!-- This is a label for a button that leads to a holistic view of all different options where the user can save their new app credential. [CHAR LIMIT=20] -->
+  <!-- This is a label for a button that leads to a holistic view of all different options where the user can save their new app credential. [CHAR LIMIT=30] -->
   <string name="string_more_options">Save another way</string>
   <!-- This is a label for a button that links to additional information about passkeys. [CHAR LIMIT=20] -->
   <string name="string_learn_more">Learn more</string>
@@ -174,4 +174,4 @@
   <!-- Text shown in the dropdown presentation to select more sign in options. [CHAR LIMIT=120] -->
   <string name="dropdown_presentation_more_sign_in_options_text">Sign-in options</string>
   <string name="more_options_content_description">More</string>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/packages/CredentialManager/res/xml/autofill_service_configuration.xml b/packages/CredentialManager/res/xml/autofill_service_configuration.xml
index 25cc094..0151add 100644
--- a/packages/CredentialManager/res/xml/autofill_service_configuration.xml
+++ b/packages/CredentialManager/res/xml/autofill_service_configuration.xml
@@ -5,6 +5,6 @@
    Note: This file is ignored for devices older that API 31
    See https://developer.android.com/about/versions/12/backup-restore
 -->
-<autofill-service-configuration
+<autofill-service
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:supportsInlineSuggestions="true"/>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index d4a8110..7bc25ed 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -174,11 +174,8 @@
                 onUserCancel()
             } else {
                 Log.d(Constants.LOG_TAG, "The provider activity was cancelled," +
-                    " re-displaying our UI.")
-                uiState = uiState.copy(
-                    selectedEntry = null,
-                    providerActivityState = ProviderActivityState.NOT_APPLICABLE,
-                )
+                            " re-displaying our UI.")
+                resetUiStateForReLaunch()
             }
         } else {
             if (entry != null) {
@@ -202,6 +199,15 @@
         }
     }
 
+    // Resets UI states for any situation that re-launches the UI
+    private fun resetUiStateForReLaunch() {
+        onBiometricPromptStateChange(BiometricPromptState.INACTIVE)
+        uiState = uiState.copy(
+            selectedEntry = null,
+            providerActivityState = ProviderActivityState.NOT_APPLICABLE,
+        )
+    }
+
     fun onLastLockedAuthEntryNotFoundError() {
         Log.d(Constants.LOG_TAG, "Unable to find the last unlocked entry")
         onInternalError()
@@ -502,4 +508,4 @@
     fun logUiEvent(uiEventEnum: UiEventEnum) {
         this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo?.packageName)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 50ebdd5..0da32bd 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -18,6 +18,7 @@
 
 import android.app.PendingIntent
 import android.app.assist.AssistStructure
+import android.content.ComponentName
 import android.content.Context
 import android.credentials.CredentialManager
 import android.credentials.GetCredentialRequest
@@ -34,6 +35,10 @@
 import android.os.OutcomeReceiver
 import android.os.ResultReceiver
 import android.service.autofill.AutofillService
+import com.android.credentialmanager.model.get.ProviderInfo
+import androidx.core.graphics.drawable.toBitmap
+import com.android.credentialmanager.model.get.ActionEntryInfo
+import com.android.credentialmanager.model.EntryInfo
 import android.service.autofill.Dataset
 import android.service.autofill.Field
 import android.service.autofill.FillCallback
@@ -63,7 +68,8 @@
 import com.android.credentialmanager.getflow.toProviderDisplayInfo
 import com.android.credentialmanager.ktx.credentialEntry
 import com.android.credentialmanager.model.CredentialType
-import com.android.credentialmanager.model.get.CredentialEntryInfo
+import java.util.ArrayList
+import java.util.Objects
 import java.util.concurrent.Executors
 import org.json.JSONException
 import org.json.JSONObject
@@ -121,8 +127,11 @@
 
         val responseClientState = Bundle()
         responseClientState.putBoolean(WEBVIEW_REQUESTED_CREDENTIAL_KEY, false)
-        val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId,
-                requestId, resultReceiver, responseClientState)
+        val uniqueAutofillIdsForRequest: MutableSet<AutofillId> = mutableSetOf()
+        val getCredRequest: GetCredentialRequest? = getCredManRequest(
+            structure, sessionId,
+            requestId, resultReceiver, responseClientState, uniqueAutofillIdsForRequest
+        )
         // TODO(b/324635774): Use callback for validating. If the request is coming
         // directly from the view, there should be a corresponding callback, otherwise
         // we should fail fast,
@@ -132,14 +141,17 @@
             return
         }
         val credentialManager: CredentialManager =
-                getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager
+            getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager
 
         val outcome = object : OutcomeReceiver<GetCandidateCredentialsResponse,
                 GetCandidateCredentialsException> {
             override fun onResult(result: GetCandidateCredentialsResponse) {
                 Log.i(TAG, "getCandidateCredentials onResult")
-                val fillResponse = convertToFillResponse(result, request,
-                    responseClientState, GetFlowUtils.extractTypePriorityMap(getCredRequest))
+                val fillResponse = convertToFillResponse(
+                    result, request,
+                    responseClientState, GetFlowUtils.extractTypePriorityMap(getCredRequest),
+                    uniqueAutofillIdsForRequest
+                )
                 if (fillResponse != null) {
                     callback.onSuccess(fillResponse)
                 } else {
@@ -195,58 +207,131 @@
 
     private fun convertToFillResponse(
             getCredResponse: GetCandidateCredentialsResponse,
-            filLRequest: FillRequest,
+            fillRequest: FillRequest,
             responseClientState: Bundle,
             typePriorityMap: Map<String, Int>,
+            uniqueAutofillIdsForRequest: MutableSet<AutofillId>
     ): FillResponse? {
         val candidateProviders = getCredResponse.candidateProviderDataList
         if (candidateProviders.isEmpty()) {
             return null
         }
-
+        val primaryProviderComponentName = getCredResponse.primaryProviderComponentName
         val entryIconMap: Map<String, Icon> = getEntryToIconMap(candidateProviders)
         val autofillIdToProvidersMap: Map<AutofillId, ArrayList<GetCredentialProviderData>> =
-                mapAutofillIdToProviders(candidateProviders)
+            mapAutofillIdToProviders(
+                uniqueAutofillIdsForRequest,
+                candidateProviders,
+                primaryProviderComponentName
+            )
         val fillResponseBuilder = FillResponse.Builder()
         fillResponseBuilder.setFlags(FillResponse.FLAG_CREDENTIAL_MANAGER_RESPONSE)
-        var validFillResponse = false
         autofillIdToProvidersMap.forEach { (autofillId, providers) ->
-            validFillResponse = processProvidersForAutofillId(
-                    filLRequest, autofillId, providers, entryIconMap, fillResponseBuilder,
-                    getCredResponse.intent, typePriorityMap)
-                    .or(validFillResponse)
+            var credentialDatasetAdded = addCredentialDatasetsForAutofillId(fillRequest,
+                autofillId, providers, entryIconMap, fillResponseBuilder, typePriorityMap)
+            if (!credentialDatasetAdded && primaryProviderComponentName != null) {
+                val providerList = GetFlowUtils.toProviderList(
+                    providers,
+                    this@CredentialAutofillService
+                )
+                val primaryProviderInfo =
+                    providerList.find { provider -> primaryProviderComponentName
+                        .flattenToString().equals(provider.id) }
+                if (primaryProviderInfo != null) {
+                    addActionDatasetsForAutofillId(
+                        fillRequest,
+                        autofillId,
+                        primaryProviderInfo,
+                        fillResponseBuilder
+                    )
+                }
+            }
         }
-        if (!validFillResponse) {
-            return null
+        for (autofillId in uniqueAutofillIdsForRequest) {
+            addMoreOptionsDataset(
+                fillRequest,
+                autofillId,
+                fillResponseBuilder,
+                getCredResponse.intent.putExtra(
+                    ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, ArrayList(candidateProviders)
+                )
+            )
         }
         fillResponseBuilder.setClientState(responseClientState)
         return fillResponseBuilder.build()
     }
 
-    private fun processProvidersForAutofillId(
-            filLRequest: FillRequest,
-            autofillId: AutofillId,
-            providerDataList: ArrayList<GetCredentialProviderData>,
-            entryIconMap: Map<String, Icon>,
-            fillResponseBuilder: FillResponse.Builder,
-            bottomSheetIntent: Intent,
-            typePriorityMap: Map<String, Int>,
+    private fun addActionDatasetsForAutofillId(
+        fillRequest: FillRequest,
+        autofillId: AutofillId,
+        primaryProvider: ProviderInfo,
+        fillResponseBuilder: FillResponse.Builder,
+    ): Boolean {
+        var index = 0
+        var datasetAdded = false
+        primaryProvider.actionEntryList.forEach { actionEntry ->
+            if (index >= maxDatasetDisplayLimit(primaryProvider.actionEntryList.size)) {
+                return@forEach
+            }
+            val pendingIntent = actionEntry.pendingIntent
+            if (pendingIntent == null) {
+                Log.e(TAG, "Pending intent for action chip is null")
+                return@forEach
+            }
+
+            val icon: Icon? = Icon.createWithBitmap(actionEntry.icon.toBitmap())
+            if (icon == null) {
+                Log.e(TAG, "Icon for action chip is null")
+                return@forEach
+            }
+
+            val presentations = constructPresentations(
+                fillRequest,
+                index,
+                actionEntry,
+                pendingIntent,
+                icon,
+                actionEntry.title,
+                actionEntry.subTitle,
+                primaryProvider.actionEntryList.size
+            )
+
+            fillResponseBuilder.addDataset(
+                Dataset.Builder()
+                    .setField(
+                        autofillId,
+                        Field.Builder().setPresentations(presentations).build()
+                    )
+                    .setAuthentication(pendingIntent.intentSender)
+                    .build()
+            )
+            datasetAdded = true
+
+            index++
+        }
+
+        return datasetAdded
+    }
+
+    private fun addCredentialDatasetsForAutofillId(
+        fillRequest: FillRequest,
+        autofillId: AutofillId,
+        providerDataList: List<GetCredentialProviderData>,
+        entryIconMap: Map<String, Icon>,
+        fillResponseBuilder: FillResponse.Builder,
+        typePriorityMap: Map<String, Int>,
     ): Boolean {
         val providerList = GetFlowUtils.toProviderList(
             providerDataList,
-            this@CredentialAutofillService)
+            this@CredentialAutofillService
+        )
         if (providerList.isEmpty()) {
             return false
         }
         val providerDisplayInfo: ProviderDisplayInfo =
-                toProviderDisplayInfo(providerList, typePriorityMap)
+            toProviderDisplayInfo(providerList, typePriorityMap)
         var totalEntryCount = providerDisplayInfo.sortedUserNameToCredentialEntryList.size
-        val inlineSuggestionsRequest = filLRequest.inlineSuggestionsRequest
-        val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
-        val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0
-        val maxDatasetDisplayLimit = this.resources.getInteger(
-                com.android.credentialmanager.R.integer.autofill_max_visible_datasets)
-                .coerceAtMost(totalEntryCount)
+
         var i = 0
         var datasetAdded = false
 
@@ -260,8 +345,6 @@
                 }
             }
         }
-        bottomSheetIntent.putExtra(
-                ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, providerDataList)
         providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@{
             val primaryEntry = it.sortedCredentialEntryList.first()
             val pendingIntent = primaryEntry.pendingIntent
@@ -271,7 +354,7 @@
                 Log.e(TAG, "PendingIntent was missing from the entry.")
                 return@usernameLoop
             }
-            if (i >= maxDatasetDisplayLimit) {
+            if (i >= maxDatasetDisplayLimit(totalEntryCount)) {
                 return@usernameLoop
             }
             val icon: Icon = if (primaryEntry.icon == null) {
@@ -280,116 +363,172 @@
                 getDefaultIcon()
             } else {
                 entryIconMap[primaryEntry.entryKey + primaryEntry.entrySubkey]
-                        ?: getDefaultIcon()
+                    ?: getDefaultIcon()
             }
-            // Create inline presentation
-            var inlinePresentation: InlinePresentation? = null
-            if (inlinePresentationSpecs != null && i < maxDatasetDisplayLimit) {
-                val spec: InlinePresentationSpec? = if (i < inlinePresentationSpecsCount) {
-                    inlinePresentationSpecs[i]
-                } else {
-                    inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
-                }
-                if (spec != null) {
-                    inlinePresentation = createInlinePresentation(primaryEntry, pendingIntent, icon,
-                            InlinePresentationsFactory.modifyInlinePresentationSpec
-                            (this@CredentialAutofillService, spec),
-                            duplicateDisplayNamesForPasskeys)
-                }
+            val displayName = primaryEntry.displayName
+            val title: String = if (primaryEntry.credentialType == CredentialType.PASSKEY &&
+                displayName != null
+            ) {
+                displayName
+            } else {
+                primaryEntry.userName
             }
-            var dropdownPresentation: RemoteViews? = null
-            if (i < maxDatasetDisplayLimit) {
-                dropdownPresentation = RemoteViewsFactory.createDropdownPresentation(
-                    this, icon, primaryEntry, /*isFirstEntry= */ i == 0,
-                    /*isLastEntry= */ (totalEntryCount - i == 1))
+            val subtitle = if (primaryEntry.credentialType ==
+                CredentialType.PASSKEY && duplicateDisplayNamesForPasskeys[title] == true
+            ) {
+                primaryEntry.userName
+            } else {
+                null
             }
-
-            val dataSetBuilder = Dataset.Builder()
-            val presentationBuilder = Presentations.Builder()
-            if (dropdownPresentation != null) {
-                presentationBuilder.setMenuPresentation(dropdownPresentation)
-            }
-            if (inlinePresentation != null) {
-                presentationBuilder.setInlinePresentation(inlinePresentation)
-            }
-
+            val presentations =
+                constructPresentations(
+                    fillRequest, i, primaryEntry, pendingIntent,
+                    icon, title, subtitle, totalEntryCount
+                )
             fillResponseBuilder.addDataset(
-                    dataSetBuilder
-                            .setField(
-                                    autofillId,
-                                    Field.Builder().setPresentations(
-                                            presentationBuilder.build())
-                                            .build())
-                            .setAuthentication(pendingIntent.intentSender)
-                            .setCredentialFillInIntent(fillInIntent)
-                            .build())
+                Dataset.Builder()
+                    .setField(
+                        autofillId,
+                        Field.Builder().setPresentations(
+                            presentations
+                        )
+                            .build()
+                    )
+                    .setAuthentication(pendingIntent.intentSender)
+                    .setCredentialFillInIntent(fillInIntent)
+                    .build()
+            )
             datasetAdded = true
             i++
         }
-        val pinnedSpec = getLastInlinePresentationSpec(inlinePresentationSpecs,
-                inlinePresentationSpecsCount)
-        if (datasetAdded) {
-            addDropdownMoreOptionsPresentation(bottomSheetIntent, autofillId, fillResponseBuilder)
-            if (pinnedSpec != null) {
-                addPinnedInlineSuggestion(pinnedSpec, autofillId,
-                        fillResponseBuilder, bottomSheetIntent)
-            }
-        }
         return datasetAdded
     }
 
-    private fun createInlinePresentation(
-            primaryEntry: CredentialEntryInfo,
-            pendingIntent: PendingIntent,
-            icon: Icon,
-            spec: InlinePresentationSpec,
-            duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>
-    ): InlinePresentation {
-        val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY &&
-            primaryEntry.displayName != null) {
-            primaryEntry.displayName!!
-        } else {
-            primaryEntry.userName
+    private fun addMoreOptionsDataset(
+        fillRequest: FillRequest,
+        autofillId: AutofillId,
+        fillResponseBuilder: FillResponse.Builder,
+        bottomSheetIntent: Intent
+    ) {
+        val inlineSuggestionsRequest = fillRequest.inlineSuggestionsRequest
+        val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
+        val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0
+        val pinnedSpec = getLastInlinePresentationSpec(
+            inlinePresentationSpecs,
+            inlinePresentationSpecsCount
+        )
+        addDropdownMoreOptionsPresentation(bottomSheetIntent, autofillId, fillResponseBuilder)
+        if (pinnedSpec != null) {
+            addPinnedInlineSuggestion(
+                pinnedSpec, autofillId,
+                fillResponseBuilder, bottomSheetIntent
+            )
         }
+    }
+
+    private fun constructPresentations(
+        fillRequest: FillRequest,
+        index: Int,
+        entry: EntryInfo,
+        pendingIntent: PendingIntent,
+        icon: Icon,
+        title: String,
+        subtitle: String?,
+        totalEntryCount: Int
+    ): Presentations {
+        val inlineSuggestionsRequest = fillRequest.inlineSuggestionsRequest
+        val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
+        val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0
+
+        // Create inline presentation
+        var inlinePresentation: InlinePresentation? = null
+        if (inlinePresentationSpecs != null && index < maxDatasetDisplayLimit(totalEntryCount)) {
+            val spec: InlinePresentationSpec? = if (index < inlinePresentationSpecsCount) {
+                inlinePresentationSpecs[index]
+            } else {
+                inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
+            }
+            if (spec != null) {
+                inlinePresentation = createInlinePresentation(
+                    pendingIntent, icon,
+                    InlinePresentationsFactory.modifyInlinePresentationSpec
+                        (this@CredentialAutofillService, spec),
+                    title, subtitle, entry is ActionEntryInfo
+                )
+            }
+        }
+        var dropdownPresentation: RemoteViews? = null
+        if (index < maxDatasetDisplayLimit(totalEntryCount)) {
+            dropdownPresentation = RemoteViewsFactory.createDropdownPresentation(
+                this, icon, entry, /*isFirstEntry= */ index == 0,
+                /*isLastEntry= */ (totalEntryCount - index == 1)
+            )
+        }
+
+        val presentationBuilder = Presentations.Builder()
+        if (dropdownPresentation != null) {
+            presentationBuilder.setMenuPresentation(dropdownPresentation)
+        }
+        if (inlinePresentation != null) {
+            presentationBuilder.setInlinePresentation(inlinePresentation)
+        }
+        return presentationBuilder.build()
+    }
+
+    private fun maxDatasetDisplayLimit(totalEntryCount: Int) = this.resources.getInteger(
+        com.android.credentialmanager.R.integer.autofill_max_visible_datasets
+    ).coerceAtMost(totalEntryCount)
+
+    private fun createInlinePresentation(
+        pendingIntent: PendingIntent,
+        icon: Icon,
+        spec: InlinePresentationSpec,
+        title: String,
+        subtitle: String?,
+        isActionEntry: Boolean
+    ): InlinePresentation {
         val sliceBuilder = InlineSuggestionUi
-                .newContentBuilder(pendingIntent)
-                .setTitle(displayName)
+            .newContentBuilder(pendingIntent)
+            .setTitle(title)
         icon.setTintBlendMode(BlendMode.DST)
         sliceBuilder.setStartIcon(icon)
-        if (primaryEntry.credentialType ==
-                CredentialType.PASSKEY && duplicateDisplayNameForPasskeys[displayName] == true) {
-            sliceBuilder.setSubtitle(primaryEntry.userName)
+        if (subtitle != null && !isActionEntry) {
+            sliceBuilder.setSubtitle(subtitle)
         }
         return InlinePresentation(
-                sliceBuilder.build().slice, spec, /* pinned= */ false)
+            sliceBuilder.build().slice, spec, /* pinned= */ false
+        )
     }
 
     private fun addDropdownMoreOptionsPresentation(
-            bottomSheetIntent: Intent,
-            autofillId: AutofillId,
-            fillResponseBuilder: FillResponse.Builder
+        bottomSheetIntent: Intent,
+        autofillId: AutofillId,
+        fillResponseBuilder: FillResponse.Builder
     ) {
         val presentationBuilder = Presentations.Builder()
-                .setMenuPresentation(
-                        RemoteViewsFactory.createMoreSignInOptionsPresentation(this))
+            .setMenuPresentation(
+                RemoteViewsFactory.createMoreSignInOptionsPresentation(this)
+            )
         val pendingIntent = setUpBottomSheetPendingIntent(bottomSheetIntent)
 
         fillResponseBuilder.addDataset(
-                Dataset.Builder()
-                        .setId(AutofillManager.PINNED_DATASET_ID)
-                        .setField(
-                                autofillId,
-                                Field.Builder().setPresentations(
-                                        presentationBuilder.build())
-                                        .build())
-                        .setAuthentication(pendingIntent.intentSender)
+            Dataset.Builder()
+                .setId(AutofillManager.PINNED_DATASET_ID)
+                .setField(
+                    autofillId,
+                    Field.Builder().setPresentations(
+                        presentationBuilder.build()
+                    )
                         .build()
+                )
+                .setAuthentication(pendingIntent.intentSender)
+                .build()
         )
     }
 
     private fun getLastInlinePresentationSpec(
-            inlinePresentationSpecs: List<InlinePresentationSpec>?,
-            inlinePresentationSpecsCount: Int
+        inlinePresentationSpecs: List<InlinePresentationSpec>?,
+        inlinePresentationSpecsCount: Int
     ): InlinePresentationSpec? {
         if (inlinePresentationSpecs != null) {
             return inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
@@ -398,40 +537,47 @@
     }
 
     private fun addPinnedInlineSuggestion(
-            spec: InlinePresentationSpec,
-            autofillId: AutofillId,
-            fillResponseBuilder: FillResponse.Builder,
-            bottomSheetIntent: Intent
+        spec: InlinePresentationSpec,
+        autofillId: AutofillId,
+        fillResponseBuilder: FillResponse.Builder,
+        bottomSheetIntent: Intent
     ) {
         val pendingIntent = setUpBottomSheetPendingIntent(bottomSheetIntent)
 
         val dataSetBuilder = Dataset.Builder()
         val sliceBuilder = InlineSuggestionUi
-                .newContentBuilder(pendingIntent)
-                .setStartIcon(Icon.createWithResource(this,
-                        com.android.credentialmanager.R.drawable.more_horiz_24px))
+            .newContentBuilder(pendingIntent)
+            .setStartIcon(
+                Icon.createWithResource(
+                    this,
+                    com.android.credentialmanager.R.drawable.more_horiz_24px
+                )
+            )
         val presentationBuilder = Presentations.Builder()
-                .setInlinePresentation(InlinePresentation(
-                        sliceBuilder.build().slice, spec, /* pinned= */ true))
+            .setInlinePresentation(
+                InlinePresentation(
+                    sliceBuilder.build().slice, spec, /* pinned= */ true
+                )
+            )
 
         fillResponseBuilder.addDataset(
-                dataSetBuilder
-                        .setId(AutofillManager.PINNED_DATASET_ID)
-                        .setField(
-                                autofillId,
-                                Field.Builder().setPresentations(
-                                        presentationBuilder.build()
-                                ).build()
-                        )
-                        .setAuthentication(pendingIntent.intentSender)
-                        .build()
+            dataSetBuilder
+                .setId(AutofillManager.PINNED_DATASET_ID)
+                .setField(
+                    autofillId,
+                    Field.Builder().setPresentations(
+                        presentationBuilder.build()
+                    ).build()
+                )
+                .setAuthentication(pendingIntent.intentSender)
+                .build()
         )
     }
 
     private fun setUpBottomSheetPendingIntent(intent: Intent): PendingIntent {
         intent.setAction(java.util.UUID.randomUUID().toString())
         return PendingIntent.getActivity(this, /*requestCode=*/0, intent,
-                PendingIntent.FLAG_MUTABLE, /*options=*/null)
+            PendingIntent.FLAG_MUTABLE, /*options=*/null)
     }
 
     /**
@@ -465,17 +611,33 @@
      *     }
      */
     private fun mapAutofillIdToProviders(
-        providerList: List<GetCredentialProviderData>
+        uniqueAutofillIdsForRequest: Set<AutofillId>,
+        providerList: List<GetCredentialProviderData>,
+        primaryProviderComponentName: ComponentName?
     ): Map<AutofillId, ArrayList<GetCredentialProviderData>> {
         val autofillIdToProviders: MutableMap<AutofillId, ArrayList<GetCredentialProviderData>> =
             mutableMapOf()
+        var primaryProvider: GetCredentialProviderData? = null
         providerList.forEach { provider ->
+            if (primaryProviderComponentName != null && Objects.equals(ComponentName
+                .unflattenFromString(provider
+                    .providerFlattenedComponentName), primaryProviderComponentName)) {
+                primaryProvider = provider
+            }
             val autofillIdToCredentialEntries:
                     MutableMap<AutofillId, ArrayList<Entry>> =
                 mapAutofillIdToCredentialEntries(provider.credentialEntries)
             autofillIdToCredentialEntries.forEach { (autofillId, entries) ->
                 autofillIdToProviders.getOrPut(autofillId) { ArrayList() }
-                        .add(copyProviderInfo(provider, entries))
+                    .add(copyProviderInfo(provider, entries))
+            }
+        }
+        // adds primary provider action entries for autofill IDs without credential entries
+        uniqueAutofillIdsForRequest.forEach { autofillId ->
+            if (!autofillIdToProviders.containsKey(autofillId) && primaryProvider != null) {
+                autofillIdToProviders.put(
+                    autofillId,
+                    ArrayList(listOf(copyProviderInfoForActionsOnly(primaryProvider!!))))
             }
         }
         return autofillIdToProviders
@@ -526,19 +688,35 @@
         )
     }
 
+    private fun copyProviderInfoForActionsOnly(
+        providerInfo: GetCredentialProviderData,
+    ): GetCredentialProviderData {
+        return GetCredentialProviderData(
+            providerInfo.providerFlattenedComponentName,
+            emptyList(),
+            providerInfo.actionChips,
+            emptyList(),
+            null
+        )
+    }
+
     override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
         TODO("Not yet implemented")
     }
 
     private fun getCredManRequest(
-            structure: AssistStructure,
-            sessionId: Int,
-            requestId: Int,
-            resultReceiver: ResultReceiver,
-            responseClientState: Bundle
+        structure: AssistStructure,
+        sessionId: Int,
+        requestId: Int,
+        resultReceiver: ResultReceiver,
+        responseClientState: Bundle,
+        uniqueAutofillIdsForRequest: MutableSet<AutofillId>
     ): GetCredentialRequest? {
         val credentialOptions: MutableList<CredentialOption> = mutableListOf()
-        traverseStructureForRequest(structure, credentialOptions, responseClientState, sessionId)
+        traverseStructureForRequest(
+            structure, credentialOptions, responseClientState,
+            sessionId, uniqueAutofillIdsForRequest
+        )
 
         if (credentialOptions.isNotEmpty()) {
             val dataBundle = Bundle()
@@ -558,7 +736,8 @@
             structure: AssistStructure,
             cmRequests: MutableList<CredentialOption>,
             responseClientState: Bundle,
-            sessionId: Int
+            sessionId: Int,
+            uniqueAutofillIdsForRequest: MutableSet<AutofillId>
     ) {
         val traversedViewNodes: MutableSet<AutofillId> = mutableSetOf()
         val credentialOptionsFromHints: MutableMap<String, CredentialOption> = mutableMapOf()
@@ -570,7 +749,7 @@
         windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
             traverseNodeForRequest(
                 windowNode.rootViewNode, cmRequests, responseClientState, traversedViewNodes,
-                credentialOptionsFromHints, sessionId)
+                credentialOptionsFromHints, sessionId, uniqueAutofillIdsForRequest)
         }
     }
 
@@ -580,7 +759,8 @@
             responseClientState: Bundle,
             traversedViewNodes: MutableSet<AutofillId>,
             credentialOptionsFromHints: MutableMap<String, CredentialOption>,
-            sessionId: Int
+            sessionId: Int,
+            uniqueAutofillIdsForRequest: MutableSet<AutofillId>
     ) {
         viewNode.autofillId?.let {
             val domain = viewNode.webDomain
@@ -590,7 +770,9 @@
                     WEBVIEW_REQUESTED_CREDENTIAL_KEY, true)
             }
             cmRequests.addAll(getCredentialOptionsFromViewNode(viewNode, it, responseClientState,
-                traversedViewNodes, credentialOptionsFromHints, sessionId))
+                traversedViewNodes, credentialOptionsFromHints, sessionId,
+                uniqueAutofillIdsForRequest)
+            )
             traversedViewNodes.add(it)
         }
 
@@ -600,8 +782,10 @@
                 }
 
         children.forEach { childNode: AssistStructure.ViewNode ->
-            traverseNodeForRequest(childNode, cmRequests, responseClientState, traversedViewNodes,
-                credentialOptionsFromHints, sessionId)
+            traverseNodeForRequest(
+                childNode, cmRequests, responseClientState, traversedViewNodes,
+                credentialOptionsFromHints, sessionId, uniqueAutofillIdsForRequest
+            )
         }
     }
 
@@ -611,7 +795,8 @@
             responseClientState: Bundle,
             traversedViewNodes: MutableSet<AutofillId>,
             credentialOptionsFromHints: MutableMap<String, CredentialOption>,
-            sessionId: Int
+            sessionId: Int,
+            uniqueAutofillIdsForRequest: MutableSet<AutofillId>
     ): MutableList<CredentialOption> {
         val credentialOptions: MutableList<CredentialOption> = mutableListOf()
         if (Flags.autofillCredmanDevIntegration() && viewNode.pendingCredentialRequest != null) {
@@ -641,8 +826,9 @@
                                 CredentialProviderService.EXTRA_AUTOFILL_ID,
                                 associatedAutofillIds
                             )
+                            uniqueAutofillIdsForRequest.addAll(associatedAutofillIds)
                         }
-            }
+                }
         }
         // TODO(b/325502552): clean up cred option logic in autofill hint
         val credentialHints: MutableList<String> = mutableListOf()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
index 7bb08d2..98e1690 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -21,8 +21,9 @@
 import com.android.credentialmanager.common.Constants
 import android.widget.RemoteViews
 import androidx.core.content.ContextCompat
+import com.android.credentialmanager.model.get.ActionEntryInfo
 import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.model.CredentialType
+import com.android.credentialmanager.model.EntryInfo
 import android.graphics.drawable.Icon
 
 class RemoteViewsFactory {
@@ -39,37 +40,47 @@
         fun createDropdownPresentation(
             context: Context,
             icon: Icon,
-            credentialEntryInfo: CredentialEntryInfo,
+            entryInfo: EntryInfo,
             isFirstEntry: Boolean,
             isLastEntry: Boolean,
         ): RemoteViews {
             var layoutId: Int = com.android.credentialmanager.R.layout
                     .credman_dropdown_presentation_layout
             val remoteViews = RemoteViews(context.packageName, layoutId)
-            val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName
-            remoteViews.setTextViewText(android.R.id.text1, displayName)
-            val secondaryText = getSecondaryText(credentialEntryInfo)
-            if (secondaryText.isNullOrBlank()) {
-                Log.w(Constants.LOG_TAG, "Secondary text for dropdown is null")
-            } else {
-                remoteViews.setTextViewText(android.R.id.text2, secondaryText)
+            if (entryInfo is CredentialEntryInfo) {
+                val displayName = entryInfo.displayName ?: entryInfo.userName
+                remoteViews.setTextViewText(android.R.id.text1, displayName)
+                val secondaryText = getSecondaryText(entryInfo)
+                if (secondaryText.isNullOrBlank()) {
+                    Log.w(Constants.LOG_TAG, "Secondary text for dropdown credential entry is null")
+                } else {
+                    remoteViews.setTextViewText(android.R.id.text2, secondaryText)
+                }
+                remoteViews.setContentDescription(
+                    android.R.id.icon1, entryInfo
+                        .providerDisplayName
+                )
+            } else if (entryInfo is ActionEntryInfo) {
+                remoteViews.setTextViewText(android.R.id.text1, entryInfo.title)
+                remoteViews.setTextViewText(android.R.id.text2, entryInfo.subTitle)
             }
-            remoteViews.setImageViewIcon(android.R.id.icon1, icon);
+            remoteViews.setImageViewIcon(android.R.id.icon1, icon)
             remoteViews.setBoolean(
-                android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true);
+                android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true
+            )
             remoteViews.setInt(
                 android.R.id.icon1,
                 SET_MAX_HEIGHT_METHOD_NAME,
                 context.resources.getDimensionPixelSize(
-                    com.android.credentialmanager.R.dimen.autofill_icon_size));
-            remoteViews.setContentDescription(android.R.id.icon1, credentialEntryInfo
-                    .providerDisplayName);
+                    com.android.credentialmanager.R.dimen.autofill_icon_size
+                )
+            )
             val drawableId =
                 if (isFirstEntry)
                     com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one else
                     com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_middle
             remoteViews.setInt(
-                android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId);
+                android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId)
             if (isFirstEntry) remoteViews.setViewPadding(
                 com.android.credentialmanager.R.id.credential_card,
                 /* left=*/0,
@@ -94,8 +105,8 @@
          * providerDisplayName. Both credential type and provider display name should not be empty.
          */
         private fun getSecondaryText(credentialEntryInfo: CredentialEntryInfo): String? {
-            return listOf(if (credentialEntryInfo.displayName != null
-                && (credentialEntryInfo.displayName != credentialEntryInfo.userName))
+            return listOf(if (credentialEntryInfo.displayName != null &&
+                (credentialEntryInfo.displayName != credentialEntryInfo.userName))
                 (credentialEntryInfo.userName) else null,
                 credentialEntryInfo.credentialTypeDisplayName,
                 credentialEntryInfo.providerDisplayName).filterNot { it.isNullOrBlank() }
@@ -113,16 +124,16 @@
                 com.android.credentialmanager
                         .R.string.dropdown_presentation_more_sign_in_options_text))
             remoteViews.setBoolean(
-                android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true);
+                android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true)
             remoteViews.setInt(
                 android.R.id.icon1,
                 SET_MAX_HEIGHT_METHOD_NAME,
                 context.resources.getDimensionPixelSize(
-                    com.android.credentialmanager.R.dimen.autofill_icon_size));
+                    com.android.credentialmanager.R.dimen.autofill_icon_size))
             val drawableId =
                 com.android.credentialmanager.R.drawable.more_options_list_item
             remoteViews.setInt(
-                android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId);
+                android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId)
             return remoteViews
         }
     }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 8e78861..19f5a99 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -292,12 +292,12 @@
         providerDisplayInfo.remoteEntry == null &&
         providerDisplayInfo.authenticationEntryList.all { it.isUnlockedAndEmpty })
         GetScreenState.UNLOCKED_AUTH_ENTRIES_ONLY
+    else if (isRequestForAllOptions)
+        GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY
     else if (providerDisplayInfo.sortedUserNameToCredentialEntryList.isEmpty() &&
         providerDisplayInfo.authenticationEntryList.isEmpty() &&
         providerDisplayInfo.remoteEntry != null)
         GetScreenState.REMOTE_ONLY
-    else if (isRequestForAllOptions)
-        GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY
     else if (isBiometricFlow(providerDisplayInfo, isFlowAutoSelectable(providerDisplayInfo)))
         GetScreenState.BIOMETRIC_SELECTION
     else GetScreenState.PRIMARY_SELECTION
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
index 0417533..473094c 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
@@ -22,6 +22,7 @@
 import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry.PerUserNameEntries
 import com.android.credentialmanager.model.CredentialType
 import com.android.credentialmanager.model.get.CredentialEntryInfo
+import java.time.Instant
 
 fun Request.Get.toGet(isPrimary: Boolean): CredentialSelectorUiState.Get {
     val accounts = providerInfos
@@ -67,4 +68,4 @@
 val comparator = compareBy<CredentialEntryInfo> { entryInfo ->
     // Passkey type always go first
     entryInfo.credentialType.let { if (it == CredentialType.PASSKEY) 0 else 1 }
-}.thenByDescending { it.lastUsedTimeMillis ?: 0 }
+}.thenByDescending { it.lastUsedTimeMillis ?: Instant.EPOCH }
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 25ac3c9..635dc42 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -172,7 +172,7 @@
 
     // This is for testing only now
     private boolean mEnableWhenCompleted;
-    private boolean mOneShot;
+    private boolean mOneShot = true;
     private boolean mHideNotification;
 
     private InstallationAsyncTask.Progress mInstallTaskProgress;
diff --git a/packages/InputDevices/res/values-am/strings.xml b/packages/InputDevices/res/values-am/strings.xml
index 9a8bd37..16f6437 100644
--- a/packages/InputDevices/res/values-am/strings.xml
+++ b/packages/InputDevices/res/values-am/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"ሞንጎሊያኛ"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ጂዮርጂያኛ"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ታይላንድኛ (ኬድማኒ)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ታይላንድኛ (ፓታሾት)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-as/strings.xml b/packages/InputDevices/res/values-as/strings.xml
index 802b13e..c57b591 100644
--- a/packages/InputDevices/res/values-as/strings.xml
+++ b/packages/InputDevices/res/values-as/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolian"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgian"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"থাই (কেডমানি)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"থাই (পাটাচ’টে)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-az/strings.xml b/packages/InputDevices/res/values-az/strings.xml
index 4a8e7ff..9c6bdb3 100644
--- a/packages/InputDevices/res/values-az/strings.xml
+++ b/packages/InputDevices/res/values-az/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Monqol"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Gürcü"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tay (Kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tay (Pattachote)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-be/strings.xml b/packages/InputDevices/res/values-be/strings.xml
index 4dd59bf..c5aa66f 100644
--- a/packages/InputDevices/res/values-be/strings.xml
+++ b/packages/InputDevices/res/values-be/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Мангольская"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Грузінская"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Тайская (Kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Тайская (Патачотэ)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-bg/strings.xml b/packages/InputDevices/res/values-bg/strings.xml
index 3dbd6f7..1260d6a 100644
--- a/packages/InputDevices/res/values-bg/strings.xml
+++ b/packages/InputDevices/res/values-bg/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"монголски"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"грузински"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"тайландски (Kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"тайландски (Pattachote)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-bn/strings.xml b/packages/InputDevices/res/values-bn/strings.xml
index 0d8e5d8..a038da9 100644
--- a/packages/InputDevices/res/values-bn/strings.xml
+++ b/packages/InputDevices/res/values-bn/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"মঙ্গোলিয়ান"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"জর্জিয়ান"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"থাই (কেডমানি)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"থাই (পাট্টাচোটে)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-cs/strings.xml b/packages/InputDevices/res/values-cs/strings.xml
index 7832e6e..9ee17e1 100644
--- a/packages/InputDevices/res/values-cs/strings.xml
+++ b/packages/InputDevices/res/values-cs/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"mongolština"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"gruzínština"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"thajština (Kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"thajština (Pattachote)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-de/strings.xml b/packages/InputDevices/res/values-de/strings.xml
index c4de5a8..3db695e 100644
--- a/packages/InputDevices/res/values-de/strings.xml
+++ b/packages/InputDevices/res/values-de/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolisch"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgisch"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thailändisch (Kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thailändisch (Pattachote)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-es-rUS/strings.xml b/packages/InputDevices/res/values-es-rUS/strings.xml
index 216e91f..7490f7d 100644
--- a/packages/InputDevices/res/values-es-rUS/strings.xml
+++ b/packages/InputDevices/res/values-es-rUS/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongol"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgiano"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tailandés (Kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tailandés (Pattachote)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-fa/strings.xml b/packages/InputDevices/res/values-fa/strings.xml
index 4fba26d..11280dd 100644
--- a/packages/InputDevices/res/values-fa/strings.xml
+++ b/packages/InputDevices/res/values-fa/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"مغولی"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"گرجستانی"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"تایلندی (کدمانی)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"تایلندی (پاتاچوته)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-fr-rCA/strings.xml b/packages/InputDevices/res/values-fr-rCA/strings.xml
index f375dad..5c931cf 100644
--- a/packages/InputDevices/res/values-fr-rCA/strings.xml
+++ b/packages/InputDevices/res/values-fr-rCA/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongol"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Géorgien"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thaï (Kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thaï (Pattachote)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-hi/strings.xml b/packages/InputDevices/res/values-hi/strings.xml
index 3fa45b1..7e3df82 100644
--- a/packages/InputDevices/res/values-hi/strings.xml
+++ b/packages/InputDevices/res/values-hi/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"मंगोलियन"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"जॉर्जियन कीबोर्ड का लेआउट"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"थाई (केडमेनी)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"थाई (पटैचोटे)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-hr/strings.xml b/packages/InputDevices/res/values-hr/strings.xml
index 409a321..ba3dc51 100644
--- a/packages/InputDevices/res/values-hr/strings.xml
+++ b/packages/InputDevices/res/values-hr/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolski"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Gruzijska"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"tajlandski (kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tajski (pattachote)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-it/strings.xml b/packages/InputDevices/res/values-it/strings.xml
index 740cf37..eed8316 100644
--- a/packages/InputDevices/res/values-it/strings.xml
+++ b/packages/InputDevices/res/values-it/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolo"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgiano"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-iw/strings.xml b/packages/InputDevices/res/values-iw/strings.xml
index 6badc3f..8cfe2cb 100644
--- a/packages/InputDevices/res/values-iw/strings.xml
+++ b/packages/InputDevices/res/values-iw/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"מונגולית"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"גיאורגית"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"‏תאית (Kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"‏תאית (Pattachote)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-ja/strings.xml b/packages/InputDevices/res/values-ja/strings.xml
index 9676886..d1b334b 100644
--- a/packages/InputDevices/res/values-ja/strings.xml
+++ b/packages/InputDevices/res/values-ja/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"モンゴル語"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ジョージア語"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"タイ語(Kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"タイ語(Pattachote)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-ka/strings.xml b/packages/InputDevices/res/values-ka/strings.xml
index 93735c9..8928f68 100644
--- a/packages/InputDevices/res/values-ka/strings.xml
+++ b/packages/InputDevices/res/values-ka/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"მონღოლური"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ქართული"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ტაილანდური (Kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ტაილანდური (Pattachote)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-km/strings.xml b/packages/InputDevices/res/values-km/strings.xml
index 0f3832e..53eb6f5 100644
--- a/packages/InputDevices/res/values-km/strings.xml
+++ b/packages/InputDevices/res/values-km/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"មុងហ្គោលី"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ហ្សក​ហ្ស៊ី"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ថៃ (Kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ថៃ (ប៉ាតាឈោត)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-kn/strings.xml b/packages/InputDevices/res/values-kn/strings.xml
index 96b9294..c743a6e 100644
--- a/packages/InputDevices/res/values-kn/strings.xml
+++ b/packages/InputDevices/res/values-kn/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"ಮಂಗೋಲಿಯನ್"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ಜಾರ್ಜಿಯನ್"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ಥಾಯ್ (ಕೆಡ್‍ಮನೀ)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ಥಾಯ್ (ಪಟ್ಟಚೋಟ್)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-ko/strings.xml b/packages/InputDevices/res/values-ko/strings.xml
index 9956f26..0e375dd 100644
--- a/packages/InputDevices/res/values-ko/strings.xml
+++ b/packages/InputDevices/res/values-ko/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"몽골어"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"조지아어"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"태국어(Kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"태국어(Pattachote)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-mk/strings.xml b/packages/InputDevices/res/values-mk/strings.xml
index 2709689..4e8be46 100644
--- a/packages/InputDevices/res/values-mk/strings.xml
+++ b/packages/InputDevices/res/values-mk/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"монголски"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"грузиски"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"тајландски (кедмани)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"тајландски (Pattachote)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-ms/strings.xml b/packages/InputDevices/res/values-ms/strings.xml
index 94ec21a..9e4c190 100644
--- a/packages/InputDevices/res/values-ms/strings.xml
+++ b/packages/InputDevices/res/values-ms/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Bahasa Mongolia"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Bahasa Georgia"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-nl/strings.xml b/packages/InputDevices/res/values-nl/strings.xml
index 1d399ae..d28ee9b 100644
--- a/packages/InputDevices/res/values-nl/strings.xml
+++ b/packages/InputDevices/res/values-nl/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongools"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgisch"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-or/strings.xml b/packages/InputDevices/res/values-or/strings.xml
index 0d0fbaf..e92c155 100644
--- a/packages/InputDevices/res/values-or/strings.xml
+++ b/packages/InputDevices/res/values-or/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"ମଙ୍ଗୋଲିଆନ୍"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ଜର୍ଜିଆନ୍"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ଥାଇ (କେଡମାନି)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ଥାଇ (ପାଟ୍ଟାଚୋଟେ)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-pt-rBR/strings.xml b/packages/InputDevices/res/values-pt-rBR/strings.xml
index e2bad4f..4a0c3be 100644
--- a/packages/InputDevices/res/values-pt-rBR/strings.xml
+++ b/packages/InputDevices/res/values-pt-rBR/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongol"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgiano"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tailandês (Kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tailandês (pattachote)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-pt-rPT/strings.xml b/packages/InputDevices/res/values-pt-rPT/strings.xml
index 7ade82f..c54b620 100644
--- a/packages/InputDevices/res/values-pt-rPT/strings.xml
+++ b/packages/InputDevices/res/values-pt-rPT/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongol"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgiano"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tailandês (Kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tailandês (Pattachote)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-pt/strings.xml b/packages/InputDevices/res/values-pt/strings.xml
index e2bad4f..4a0c3be 100644
--- a/packages/InputDevices/res/values-pt/strings.xml
+++ b/packages/InputDevices/res/values-pt/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongol"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgiano"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tailandês (Kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tailandês (pattachote)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-si/strings.xml b/packages/InputDevices/res/values-si/strings.xml
index 27b031b..97aed62 100644
--- a/packages/InputDevices/res/values-si/strings.xml
+++ b/packages/InputDevices/res/values-si/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"මොන්ගෝලියානු"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ජෝර්ජියානු"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"තායි (කෙඩ්මනී)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"තායි (පට්ටචෝටේ)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-sv/strings.xml b/packages/InputDevices/res/values-sv/strings.xml
index f29b665..3d08415 100644
--- a/packages/InputDevices/res/values-sv/strings.xml
+++ b/packages/InputDevices/res/values-sv/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"mongoliska"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"georgiska"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"thailändska (pattachote)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-sw/strings.xml b/packages/InputDevices/res/values-sw/strings.xml
index ade2c28..42714a5 100644
--- a/packages/InputDevices/res/values-sw/strings.xml
+++ b/packages/InputDevices/res/values-sw/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Kimongolia"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Kijojia"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Kithai (Kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Kitai (Kipatachote)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-ta/strings.xml b/packages/InputDevices/res/values-ta/strings.xml
index 14a17ad..f8bc751 100644
--- a/packages/InputDevices/res/values-ta/strings.xml
+++ b/packages/InputDevices/res/values-ta/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"மங்கோலியன்"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ஜார்ஜியன்"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"தாய் (கேட்மேனி)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"தாய் (பட்டாசொட்டே)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-te/strings.xml b/packages/InputDevices/res/values-te/strings.xml
index 676dd92..2c1c1f8 100644
--- a/packages/InputDevices/res/values-te/strings.xml
+++ b/packages/InputDevices/res/values-te/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"మంగోలియన్"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"జార్జియన్"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"థాయ్ (కెడ్మనీ)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"థాయ్ (పత్తచోత్)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-th/strings.xml b/packages/InputDevices/res/values-th/strings.xml
index 2473128..3b96226 100644
--- a/packages/InputDevices/res/values-th/strings.xml
+++ b/packages/InputDevices/res/values-th/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"ภาษามองโกเลีย"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ภาษาจอร์เจีย"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ไทย (เกษมณี)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ไทย (ปัตตะโชติ)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-tr/strings.xml b/packages/InputDevices/res/values-tr/strings.xml
index ba4703c..a5c89d7 100644
--- a/packages/InputDevices/res/values-tr/strings.xml
+++ b/packages/InputDevices/res/values-tr/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Moğolca"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Gürcüce"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tayca (Kedmanee)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tayca (Pattachote)"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-uk/strings.xml b/packages/InputDevices/res/values-uk/strings.xml
index c74b1c1..dd3aab8 100644
--- a/packages/InputDevices/res/values-uk/strings.xml
+++ b/packages/InputDevices/res/values-uk/strings.xml
@@ -51,6 +51,5 @@
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Монгольська"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Грузинська"</string>
     <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Тайська (кедмані)"</string>
-    <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) -->
-    <skip />
+    <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Тайська (паттачоте)"</string>
 </resources>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
index eef21991..c96644c 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
@@ -23,23 +23,23 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.net.Uri;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.View;
-
 import androidx.annotation.Nullable;
 
 /**
  * Installation failed: Return status code to the caller or display failure UI to user
  */
 public class InstallFailed extends Activity {
+
     private static final String LOG_TAG = InstallFailed.class.getSimpleName();
 
-    /** Label of the app that failed to install */
+    /**
+     * Label of the app that failed to install
+     */
     private CharSequence mLabel;
 
     private AlertDialog mDialog;
@@ -80,29 +80,29 @@
 
         setFinishOnTouchOutside(true);
 
-        int statusCode = getIntent().getIntExtra(PackageInstaller.EXTRA_STATUS,
-                PackageInstaller.STATUS_FAILURE);
+        Intent intent = getIntent();
+        int statusCode = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
+            PackageInstaller.STATUS_FAILURE);
+        boolean returnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
 
-        if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
-            int legacyStatus = getIntent().getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS,
-                    PackageManager.INSTALL_FAILED_INTERNAL_ERROR);
+        if (returnResult) {
+            int legacyStatus = intent.getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS,
+                PackageManager.INSTALL_FAILED_INTERNAL_ERROR);
 
             // Return result if requested
             Intent result = new Intent();
             result.putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus);
             setResult(Activity.RESULT_FIRST_USER, result);
             finish();
-        } else {
-            Intent intent = getIntent();
-            ApplicationInfo appInfo = intent
-                    .getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
-            Uri packageURI = intent.getData();
+        } else if (statusCode != PackageInstaller.STATUS_FAILURE_ABORTED) {
+            // statusCode will be STATUS_FAILURE_ABORTED if the update-owner confirmation dialog was
+            // dismissed by the user. We don't want to show a InstallFailed dialog in this case.
+            // If the user denies install permission for normal installs, this dialog will never be
+            // triggered as the status code is returned from PackageInstallerActivity.java
 
             // Set header icon and title
-            PackageUtil.AppSnippet as;
-            PackageManager pm = getPackageManager();
-            as = intent.getParcelableExtra(PackageInstallerActivity.EXTRA_APP_SNIPPET,
-                    PackageUtil.AppSnippet.class);
+            PackageUtil.AppSnippet as = intent.getParcelableExtra(
+                PackageInstallerActivity.EXTRA_APP_SNIPPET, PackageUtil.AppSnippet.class);
 
             // Store label for dialog
             mLabel = as.label;
@@ -127,6 +127,8 @@
 
             // Get status messages
             setExplanationFromErrorCode(statusCode);
+        } else {
+            finish();
         }
     }
 
@@ -135,6 +137,7 @@
      * "manage applications" settings page.
      */
     public static class OutOfSpaceDialog extends DialogFragment {
+
         private InstallFailed mActivity;
 
         @Override
@@ -147,16 +150,16 @@
         @Override
         public Dialog onCreateDialog(Bundle savedInstanceState) {
             return new AlertDialog.Builder(mActivity)
-                    .setTitle(R.string.out_of_space_dlg_title)
-                    .setMessage(getString(R.string.out_of_space_dlg_text, mActivity.mLabel))
-                    .setPositiveButton(R.string.manage_applications, (dialog, which) -> {
-                        // launch manage applications
-                        Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE");
-                        startActivity(intent);
-                        mActivity.finish();
-                    })
-                    .setNegativeButton(R.string.cancel, (dialog, which) -> mActivity.finish())
-                    .create();
+                .setTitle(R.string.out_of_space_dlg_title)
+                .setMessage(getString(R.string.out_of_space_dlg_text, mActivity.mLabel))
+                .setPositiveButton(R.string.manage_applications, (dialog, which) -> {
+                    // launch manage applications
+                    Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE");
+                    startActivity(intent);
+                    mActivity.finish();
+                })
+                .setNegativeButton(R.string.cancel, (dialog, which) -> mActivity.finish())
+                .create();
         }
 
         @Override
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index 1a6c2bb..59a511d 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -30,6 +30,8 @@
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.util.Log;
 import android.view.View;
 import android.widget.Button;
@@ -91,8 +93,11 @@
             // ContentResolver.SCHEME_FILE
             // STAGED_SESSION_ID extra contains an ID of a previously staged install session.
             final File sourceFile = new File(mPackageURI.getPath());
-            PackageUtil.AppSnippet as = getIntent()
-                    .getParcelableExtra(EXTRA_APP_SNIPPET, PackageUtil.AppSnippet.class);
+
+            // Dialogs displayed while changing update-owner have a blank icon. To fix this,
+            // fetch the appSnippet from the source file again
+            PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
+            getIntent().putExtra(EXTRA_APP_SNIPPET, as);
 
             AlertDialog.Builder builder = new AlertDialog.Builder(this);
 
@@ -244,6 +249,14 @@
         super.onDestroy();
     }
 
+    @Override
+    public void finish() {
+        if (mDialog != null) {
+            mDialog.dismiss();
+        }
+        super.finish();
+    }
+
     /**
      * Launch the appropriate finish activity (success or failed) for the installation result.
      *
@@ -299,7 +312,11 @@
                         PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
 
                 try {
-                    session.commit(pendingIntent.getIntentSender());
+                    // Delay committing the session by 100ms to fix a UI glitch while displaying the
+                    // Update-Owner change dialog on top of the Installing dialog
+                    new Handler(Looper.getMainLooper()).postDelayed(() -> {
+                        session.commit(pendingIntent.getIntentSender());
+                    }, 100);
                 } catch (Exception e) {
                     Log.e(LOG_TAG, "Cannot install package: ", e);
                     launchFailure(PackageInstaller.STATUS_FAILURE,
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
index cf2f85e..13251d8 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
@@ -165,7 +165,9 @@
         if (mStagingTask != null) {
             mStagingTask.cancel(true);
         }
-
+        if (mDialog != null) {
+            mDialog.dismiss();
+        }
         super.onDestroy();
     }
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index a4c6ac7..3fea599 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -193,6 +193,7 @@
 
         if (isSessionInstall) {
             nextActivity.setClass(this, PackageInstallerActivity.class);
+            nextActivity.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
         } else {
             Uri packageUri = intent.getData();
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 8bed945..e0398aa 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -176,11 +176,14 @@
     }
 
     private CharSequence getExistingUpdateOwnerLabel() {
+        return getApplicationLabel(getExistingUpdateOwner());
+    }
+
+    private String getExistingUpdateOwner() {
         try {
             final String packageName = mPkgInfo.packageName;
             final InstallSourceInfo sourceInfo = mPm.getInstallSourceInfo(packageName);
-            final String existingUpdateOwner = sourceInfo.getUpdateOwnerPackageName();
-            return getApplicationLabel(existingUpdateOwner);
+            return sourceInfo.getUpdateOwnerPackageName();
         } catch (NameNotFoundException e) {
             return null;
         }
@@ -299,6 +302,18 @@
     }
 
     private void initiateInstall() {
+        final String existingUpdateOwner = getExistingUpdateOwner();
+        if (mSessionId == SessionInfo.INVALID_ID &&
+            !TextUtils.isEmpty(existingUpdateOwner) &&
+            !TextUtils.equals(existingUpdateOwner, mOriginatingPackage)) {
+            // Since update ownership is being changed, the system will request another
+            // user confirmation shortly. Thus, we don't need to ask the user to confirm
+            // installation here.
+            startInstall();
+            return;
+        }
+
+        // Proceed with user confirmation as we are not changing the update-owner in this install.
         String pkgName = mPkgInfo.packageName;
         // Check if there is already a package on the device with this name
         // but it has been renamed to something else.
@@ -465,10 +480,13 @@
 
     @Override
     protected void onDestroy() {
-        super.onDestroy();
         while (!mActiveUnknownSourcesListeners.isEmpty()) {
             unregister(mActiveUnknownSourcesListeners.get(0));
         }
+        if (mDialog != null) {
+            mDialog.dismiss();
+        }
+        super.onDestroy();
     }
 
     private void bindUi() {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index f7752ff..2e9b7b4 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -420,25 +420,51 @@
      *      * If AppOP is granted and user action is required to proceed with install
      *      * If AppOp grant is to be requested from the user
      */
-    fun requestUserConfirmation(): InstallStage {
+    fun requestUserConfirmation(): InstallStage? {
         return if (isTrustedSource) {
             if (localLogv) {
                 Log.i(LOG_TAG, "Install allowed")
             }
-            // Returns InstallUserActionRequired stage if install details could be successfully
-            // computed, else it returns InstallAborted.
-            generateConfirmationSnippet()
+            maybeDeferUserConfirmation()
         } else {
             val unknownSourceStage = handleUnknownSources(appOpRequestInfo)
             if (unknownSourceStage.stageCode == InstallStage.STAGE_READY) {
                 // Source app already has appOp granted.
-                generateConfirmationSnippet()
+                maybeDeferUserConfirmation()
             } else {
                 unknownSourceStage
             }
         }
     }
 
+    /**
+     *  If the update-owner for the incoming app is being changed, defer confirming with the
+     *  user and directly proceed with the install. The system will request another
+     *  user confirmation shortly.
+     */
+    private fun maybeDeferUserConfirmation(): InstallStage? {
+        // Returns InstallUserActionRequired stage if install details could be successfully
+        // computed, else it returns InstallAborted.
+        val confirmationSnippet: InstallStage = generateConfirmationSnippet()
+        if (confirmationSnippet.stageCode == InstallStage.STAGE_ABORTED) {
+            return confirmationSnippet
+        }
+
+        val existingUpdateOwner: CharSequence? = getExistingUpdateOwner(newPackageInfo!!)
+        return if (sessionId == SessionInfo.INVALID_ID &&
+            !TextUtils.isEmpty(existingUpdateOwner) &&
+            !TextUtils.equals(existingUpdateOwner, callingPackage)
+        ) {
+            // Since update ownership is being changed, the system will request another
+            // user confirmation shortly. Thus, we don't need to ask the user to confirm
+            // installation here.
+            initiateInstall()
+            null
+        } else {
+            confirmationSnippet
+        }
+    }
+
     private fun generateConfirmationSnippet(): InstallStage {
         val packageSource: Any?
         val pendingUserActionReason: Int
@@ -639,11 +665,14 @@
     }
 
     private fun getExistingUpdateOwnerLabel(pkgInfo: PackageInfo): CharSequence? {
+        return getApplicationLabel(getExistingUpdateOwner(pkgInfo))
+    }
+
+    private fun getExistingUpdateOwner(pkgInfo: PackageInfo): String? {
         return try {
             val packageName = pkgInfo.packageName
             val sourceInfo = packageManager.getInstallSourceInfo(packageName)
-            val existingUpdateOwner = sourceInfo.updateOwnerPackageName
-            getApplicationLabel(existingUpdateOwner)
+            sourceInfo.updateOwnerPackageName
         } catch (e: PackageManager.NameNotFoundException) {
             null
         }
@@ -861,7 +890,12 @@
             }
             _installResult.setValue(InstallSuccess(appSnippet, shouldReturnResult, resultIntent))
         } else {
-            _installResult.setValue(InstallFailed(appSnippet, statusCode, legacyStatus, message))
+            if (statusCode != PackageInstaller.STATUS_FAILURE_ABORTED) {
+                _installResult.setValue(InstallFailed(appSnippet, statusCode, legacyStatus, message))
+            } else {
+                _installResult.setValue(InstallAborted(ABORT_REASON_INTERNAL_ERROR))
+            }
+
         }
     }
 
@@ -889,8 +923,8 @@
      * When the identity of the install source could not be determined, user can skip checking the
      * source and directly proceed with the install.
      */
-    fun forcedSkipSourceCheck(): InstallStage {
-        return generateConfirmationSnippet()
+    fun forcedSkipSourceCheck(): InstallStage? {
+        return maybeDeferUserConfirmation()
     }
 
     val stagingProgress: LiveData<Int>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt
index 072fb2d..388e03f 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt
@@ -22,6 +22,7 @@
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MediatorLiveData
 import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.distinctUntilChanged
 import com.android.packageinstaller.v2.model.InstallRepository
 import com.android.packageinstaller.v2.model.InstallStage
 import com.android.packageinstaller.v2.model.InstallStaging
@@ -37,6 +38,19 @@
     val currentInstallStage: MutableLiveData<InstallStage>
         get() = _currentInstallStage
 
+    init {
+        // Since installing is an async operation, we may get the install result later in time.
+        // Result of the installation will be set in InstallRepository#installResult.
+        // As such, currentInstallStage will need to add another MutableLiveData as a data source
+        _currentInstallStage.addSource(
+            repository.installResult.distinctUntilChanged()
+        ) { installStage: InstallStage? ->
+            if (installStage != null) {
+                _currentInstallStage.value = installStage
+            }
+        }
+    }
+
     fun preprocessIntent(intent: Intent, callerInfo: InstallRepository.CallerInfo) {
         val stage = repository.performPreInstallChecks(intent, callerInfo)
         if (stage.stageCode == InstallStage.STAGE_ABORTED) {
@@ -62,12 +76,16 @@
 
     private fun checkIfAllowedAndInitiateInstall() {
         val stage = repository.requestUserConfirmation()
-        _currentInstallStage.value = stage
+        if (stage != null) {
+            _currentInstallStage.value = stage
+        }
     }
 
     fun forcedSkipSourceCheck() {
         val stage = repository.forcedSkipSourceCheck()
-        _currentInstallStage.value = stage
+        if (stage != null) {
+            _currentInstallStage.value = stage
+        }
     }
 
     fun cleanupInstall() {
@@ -80,15 +98,7 @@
     }
 
     fun initiateInstall() {
-        // Since installing is an async operation, we will get the install result later in time.
-        // Result of the installation will be set in InstallRepository#mInstallResult.
-        // As such, mCurrentInstallStage will need to add another MutableLiveData as a data source
         repository.initiateInstall()
-        _currentInstallStage.addSource(repository.installResult) { installStage: InstallStage? ->
-            if (installStage != null) {
-                _currentInstallStage.value = installStage
-            }
-        }
     }
 
     val stagedSessionId: Int
diff --git a/packages/PrintSpooler/TEST_MAPPING b/packages/PrintSpooler/TEST_MAPPING
index 4fa8822..ad3b44f 100644
--- a/packages/PrintSpooler/TEST_MAPPING
+++ b/packages/PrintSpooler/TEST_MAPPING
@@ -8,5 +8,10 @@
         }
       ]
     }
+  ],
+  "postsubmit": [
+    {
+      "name": "PrintSpoolerOutOfProcessTests"
+    }
   ]
 }
diff --git a/packages/PrintSpooler/res/values-night/themes.xml b/packages/PrintSpooler/res/values-night/themes.xml
index 3cc64a6..76fa7b9 100644
--- a/packages/PrintSpooler/res/values-night/themes.xml
+++ b/packages/PrintSpooler/res/values-night/themes.xml
@@ -24,6 +24,7 @@
     <style name="Theme.SelectPrinterActivity"
            parent="android:style/Theme.DeviceDefault">
         <item name="android:textAppearanceListItemSecondary">@style/ListItemSecondary</item>
+        <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
     </style>
 
     <style name="Theme.PrintActivity" parent="@android:style/Theme.DeviceDefault">
diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml
index bd96025..22842f7 100644
--- a/packages/PrintSpooler/res/values/themes.xml
+++ b/packages/PrintSpooler/res/values/themes.xml
@@ -24,6 +24,7 @@
            parent="android:style/Theme.DeviceDefault.Light">
         <item name="android:textAppearanceListItemSecondary">@style/ListItemSecondary</item>
         <item name="android:windowLightStatusBar">true</item>
+        <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
     </style>
 
     <style name="Theme.PrintActivity" parent="@android:style/Theme.DeviceDefault.Light">
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index d25d5dc..ff09084 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -785,6 +785,9 @@
                 } else {
                     onPrinterUnavailable(printerInfo);
                 }
+                if (mPrinterRegistry != null) {
+                    mPrinterRegistry.setTrackedPrinter(mCurrentPrinter.getId());
+                }
 
                 mDestinationSpinnerAdapter.ensurePrinterInVisibleAdapterPosition(printerInfo);
 
diff --git a/packages/SettingsLib/DataStore/README.md b/packages/SettingsLib/DataStore/README.md
index 30cb993..a762ad3 100644
--- a/packages/SettingsLib/DataStore/README.md
+++ b/packages/SettingsLib/DataStore/README.md
@@ -1,55 +1,93 @@
 # Datastore library
 
-This library aims to manage datastore in a consistent way.
+This library provides consistent API for data management (including backup,
+restore, and metrics logging) on Android platform.
+
+Notably, it is designed to be flexible and could be utilized for a wide range of
+data store besides the settings preferences.
 
 ## Overview
 
-A datastore is required to extend the `BackupRestoreStorage` class and implement
-either `Observable` or `KeyedObservable` interface, which enforces:
+In the high-level design, a persistent datastore aims to support two key
+characteristics:
 
--   Backup and restore: Datastore should support
-    [data backup](https://developer.android.com/guide/topics/data/backup) to
-    preserve user experiences on a new device.
--   Observer pattern: The
-    [observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) allows to
-    monitor data change in the datastore and
-    -   trigger
-        [BackupManager.dataChanged](https://developer.android.com/reference/android/app/backup/BackupManager#dataChanged\(\))
-        automatically.
-    -   track data change event to log metrics.
-    -   update internal state and take action.
+-   **observable**: triggers backup and metrics logging whenever data is
+    changed.
+-   **transferable**: offers users with a seamless experience by backing up and
+    restoring data on to new devices.
+
+More specifically, Android framework supports
+[data backup](https://developer.android.com/guide/topics/data/backup) to
+preserve user experiences on a new device. And the
+[observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) allows to
+monitor data change.
 
 ### Backup and restore
 
-The Android backup framework provides
+Currently, the Android backup framework provides
 [BackupAgentHelper](https://developer.android.com/reference/android/app/backup/BackupAgentHelper)
 and
 [BackupHelper](https://developer.android.com/reference/android/app/backup/BackupHelper)
-to back up a datastore. However, there are several caveats when implement
-`BackupHelper`:
+to facilitate data backup. However, there are several caveats to consider when
+implementing `BackupHelper`:
 
--   performBackup: The data is updated incrementally but it is not well
+-   *performBackup*: The data is updated incrementally but it is not well
     documented. The `ParcelFileDescriptor` state parameters are normally ignored
     and data is updated even there is no change.
--   restoreEntity: The implementation must take care not to seek or close the
-    underlying data source, nor read more than size() bytes from the stream when
-    restore (see
+-   *restoreEntity*: The implementation must take care not to seek or close the
+    underlying data source, nor read more than `size()` bytes from the stream
+    when restore (see
     [BackupDataInputStream](https://developer.android.com/reference/android/app/backup/BackupDataInputStream)).
-    It is possible a `BackupHelper` prevents other `BackupHelper`s from
-    restoring data.
--   writeNewStateDescription: Existing implementations rarely notice that this
-    callback is invoked after all entities are restored, and check if necessary
-    data are all restored in `restoreEntity` (e.g.
+    It is possible that a `BackupHelper` interferes with the restore process of
+    other `BackupHelper`s.
+-   *writeNewStateDescription*: Existing implementations rarely notice that this
+    callback is invoked after *all* entities are restored. Instead, they check
+    if necessary data are all restored in the `restoreEntity` (e.g.
     [BatteryBackupHelper](https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Settings/src/com/android/settings/fuelgauge/BatteryBackupHelper.java;l=144;drc=cca804e1ed504e2d477be1e3db00fb881ca32736)),
     which is not robust sometimes.
 
-This library provides more clear API and offers some improvements:
+The datastore library will mitigate these problems by providing alternative
+APIs. For instance, library users make use of `InputStream` / `OutputStream` to
+back up and restore data directly.
 
--   The implementation only needs to focus on the `BackupRestoreEntity`
-    interface. The `InputStream` of restore will ensure bounded data are read,
-    and close the stream will be no-op.
--   The library computes checksum of the backup data automatically, so that
-    unchanged data will not be sent to Android backup system.
+### Observer pattern
+
+In the current implementation, the Android backup framework requires a manual
+call to
+[BackupManager.dataChanged](https://developer.android.com/reference/android/app/backup/BackupManager#dataChanged\(\)).
+However, it's often observed that this API call is forgotten when using
+`SharedPreferences`. Additionally, there's a common need to log metrics when
+data changed. To address these limitations, datastore API employed the observer
+pattern.
+
+### API design and advantages
+
+Datastore must extend the `BackupRestoreStorage` class (subclass of
+[BackupHelper](https://developer.android.com/reference/android/app/backup/BackupHelper)).
+The data in a datastore is group by entity, which is represented by
+`BackupRestoreEntity`. Basically, a datastore implementation only needs to focus
+on the `BackupRestoreEntity`.
+
+If the datastore is key-value based (e.g. `SharedPreferences`), implements the
+`KeyedObservable` interface to offer fine-grained observer. Otherwise,
+implements `Observable`. There are builtin thread-safe implementations of the
+two interfaces (`KeyedDataObservable` / `DataObservable`). If it is Kotlin, use
+delegation to simplify the code.
+
+Keep in mind that the implementation should call `KeyedObservable.notifyChange`
+/ `Observable.notifyChange` whenever internal data is changed, so that the
+registered observer will be notified properly.
+
+For `SharedPreferences` use case, leverage the `SharedPreferencesStorage`
+directly. To back up other file based storage, extend the
+`BackupRestoreFileStorage` class.
+
+Here are some highlights of the library:
+
+-   The restore `InputStream` will ensure bounded data are read, and close the
+    stream is no-op. That being said, all entities are isolated.
+-   Data checksum is computed automatically, unchanged data will not be sent to
+    Android backup system.
 -   Data compression is supported:
     -   ZIP best compression is enabled by default, no extra effort needs to be
         taken.
@@ -67,98 +105,159 @@
     successfully restored in those older versions. This is achieved by extending
     the `BackupRestoreFileStorage` class, and `BackupRestoreFileArchiver` will
     treat each file as an entity and do the backup / restore.
--   Manual `BackupManager.dataChanged` call is unnecessary now, the library will
-    do the invocation (see next section).
+-   Manual `BackupManager.dataChanged` call is unnecessary now, the framework
+    will invoke the API automatically.
 
-### Observer pattern
+## Usages
 
-Manual `BackupManager.dataChanged` call is required by current backup framework.
-In practice, it is found that `SharedPreferences` usages foget to invoke the
-API. Besides, there are common use cases to log metrics when data is changed.
-Consequently, observer pattern is employed to resolve the issues.
+This section provides [examples](example/ExampleStorage.kt) of datastore.
 
-If the datastore is key-value based (e.g. `SharedPreferences`), implements the
-`KeyedObservable` interface to offer fine-grained observer. Otherwise,
-implements `Observable`. The library provides thread-safe implementations
-(`KeyedDataObservable` / `DataObservable`), and Kotlin delegation will be
-helpful.
-
-Keep in mind that the implementation should call `KeyedObservable.notifyChange`
-/ `Observable.notifyChange` whenever internal data is changed, so that the
-registered observer will be notified properly.
-
-## Usage and example
-
-For `SharedPreferences` use case, leverage the `SharedPreferencesStorage`. To
-back up other file based storage, extend the `BackupRestoreFileStorage` class.
-
-Here is an example of customized datastore, which has a string to back up:
+Here is a datastore with a string data:
 
 ```kotlin
-class MyDataStore : ObservableBackupRestoreStorage() {
-    // Another option is make it a StringEntity type and maintain a String field inside StringEntity
-    @Volatile // backup/restore happens on Binder thread
-    var data: String? = null
-        private set
+class ExampleStorage : ObservableBackupRestoreStorage() {
+  @Volatile // field is manipulated by multiple threads, synchronization might be needed
+  var data: String? = null
+    private set
 
-    fun setData(data: String?) {
-        this.data = data
-        notifyChange(ChangeReason.UPDATE)
+  @AnyThread
+  fun setData(data: String?) {
+    this.data = data
+    // call notifyChange to trigger backup and metrics logging whenever data is changed
+    if (data != null) {
+      notifyChange(ChangeReason.UPDATE)
+    } else {
+      notifyChange(ChangeReason.DELETE)
     }
-
-    override val name: String
-        get() = "MyData"
-
-    override fun createBackupRestoreEntities(): List<BackupRestoreEntity> =
-        listOf(StringEntity("data"))
-
-    private inner class StringEntity(override val key: String) : BackupRestoreEntity {
-        override fun backup(
-            backupContext: BackupContext,
-            outputStream: OutputStream,
-        ) =
-            if (data != null) {
-                outputStream.write(data!!.toByteArray(UTF_8))
-                EntityBackupResult.UPDATE
-            } else {
-                EntityBackupResult.DELETE
-            }
-
-        override fun restore(restoreContext: RestoreContext, inputStream: InputStream) {
-            data = String(inputStream.readAllBytes(), UTF_8)
-            // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
-        }
-    }
-
-    override fun onRestoreFinished() {
-        // TODO: Update state with the restored data. Use this callback instead "restore()" in case
-        //       the restore action involves several entities.
-        // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
-    }
-}
-```
-
-In the application class:
-
-```kotlin
-class MyApplication : Application() {
-  override fun onCreate() {
-    super.onCreate();
-    BackupRestoreStorageManager.getInstance(this).add(MyDataStore());
   }
-}
-```
 
-In the custom `BackupAgentHelper` class:
+  override val name: String
+    get() = "ExampleStorage"
 
-```kotlin
-class MyBackupAgentHelper : BackupAgentHelper() {
-  override fun onCreate() {
-    BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this);
+  override fun createBackupRestoreEntities(): List<BackupRestoreEntity> =
+    listOf(StringEntity("data"))
+
+  override fun enableRestore(): Boolean {
+    return true // check condition like flag, environment, etc.
+  }
+
+  override fun enableBackup(backupContext: BackupContext): Boolean {
+    return true // check condition like flag, environment, etc.
+  }
+
+  @BinderThread
+  private inner class StringEntity(override val key: String) : BackupRestoreEntity {
+    override fun backup(backupContext: BackupContext, outputStream: OutputStream) =
+      if (data != null) {
+        outputStream.write(data!!.toByteArray(UTF_8))
+        EntityBackupResult.UPDATE
+      } else {
+        EntityBackupResult.DELETE // delete existing backup data
+      }
+
+    override fun restore(restoreContext: RestoreContext, inputStream: InputStream) {
+      // DO NOT call setData API here, which will trigger notifyChange unexpectedly.
+      // Under the hood, the datastore library will call notifyChange(ChangeReason.RESTORE)
+      // later to notify observers.
+      data = String(inputStream.readBytes(), UTF_8)
+      // Handle restored data in onRestoreFinished() callback
+    }
   }
 
   override fun onRestoreFinished() {
-    BackupRestoreStorageManager.getInstance(this).onRestoreFinished();
+    // TODO: Update state with the restored data. Use this callback instead of "restore()" in
+    //       case the restore action involves several entities.
+    // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
   }
 }
 ```
+
+And this is a datastore with key value data:
+
+```kotlin
+class ExampleKeyValueStorage :
+  BackupRestoreStorage(), KeyedObservable<String> by KeyedDataObservable() {
+  // thread safe data structure
+  private val map = ConcurrentHashMap<String, String>()
+
+  override val name: String
+    get() = "ExampleKeyValueStorage"
+
+  fun updateData(key: String, value: String?) {
+    if (value != null) {
+      map[key] = value
+      notifyChange(ChangeReason.UPDATE)
+    } else {
+      map.remove(key)
+      notifyChange(ChangeReason.DELETE)
+    }
+  }
+
+  override fun createBackupRestoreEntities(): List<BackupRestoreEntity> =
+    listOf(createMapBackupRestoreEntity())
+
+  private fun createMapBackupRestoreEntity() =
+    object : BackupRestoreEntity {
+      override val key: String
+        get() = "map"
+
+      override fun backup(
+        backupContext: BackupContext,
+        outputStream: OutputStream,
+      ): EntityBackupResult {
+        // Use TreeMap to achieve predictable and stable order, so that data will not be
+        // updated to Android backup backend if there is only order change.
+        val copy = TreeMap(map)
+        if (copy.isEmpty()) return EntityBackupResult.DELETE
+        val dataOutputStream = DataOutputStream(outputStream)
+        dataOutputStream.writeInt(copy.size)
+        for ((key, value) in copy) {
+          dataOutputStream.writeUTF(key)
+          dataOutputStream.writeUTF(value)
+        }
+        return EntityBackupResult.UPDATE
+      }
+
+      override fun restore(restoreContext: RestoreContext, inputStream: InputStream) {
+        val dataInputString = DataInputStream(inputStream)
+        repeat(dataInputString.readInt()) {
+          val key = dataInputString.readUTF()
+          val value = dataInputString.readUTF()
+          map[key] = value
+        }
+      }
+    }
+}
+```
+
+All the datastore should be added in the application class:
+
+```kotlin
+class ExampleApplication : Application() {
+  override fun onCreate() {
+    super.onCreate()
+    BackupRestoreStorageManager.getInstance(this)
+      .add(ExampleStorage(), ExampleKeyValueStorage())
+  }
+}
+```
+
+Additionally, inject datastore to the custom `BackupAgentHelper` class:
+
+```kotlin
+class ExampleBackupAgent : BackupAgentHelper() {
+  override fun onCreate() {
+    super.onCreate()
+    BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this)
+  }
+
+  override fun onRestoreFinished() {
+    BackupRestoreStorageManager.getInstance(this).onRestoreFinished()
+  }
+}
+```
+
+## Development
+
+Please preserve the code coverage ratio during development. The current line
+coverage is **100% (444/444)** and branch coverage is **93.6% (176/188)**.
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt
index 817ee4c..6720e5c 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt
@@ -23,7 +23,11 @@
 import java.io.InputStream
 import java.io.OutputStream
 
-/** Entity for back up and restore. */
+/**
+ * Entity for back up and restore.
+ *
+ * Note that backup/restore callback is invoked on the binder thread.
+ */
 interface BackupRestoreEntity {
     /**
      * Key of the entity.
@@ -45,9 +49,12 @@
     /**
      * Backs up the entity.
      *
+     * Back up data in predictable order (e.g. use `TreeMap` instead of `HashMap`), otherwise data
+     * will be backed up needlessly.
+     *
      * @param backupContext context for backup
      * @param outputStream output stream to back up data
-     * @return false if backup file is deleted, otherwise true
+     * @return backup result
      */
     @BinderThread
     @Throws(IOException::class)
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
index 935f9cc..284c97b 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
@@ -22,6 +22,7 @@
 import android.app.backup.BackupHelper
 import android.os.ParcelFileDescriptor
 import android.util.Log
+import androidx.annotation.BinderThread
 import androidx.annotation.VisibleForTesting
 import androidx.collection.MutableScatterMap
 import com.google.common.io.ByteStreams
@@ -38,16 +39,22 @@
 import java.util.zip.CheckedInputStream
 import java.util.zip.CheckedOutputStream
 import java.util.zip.Checksum
+import javax.annotation.concurrent.ThreadSafe
 
 internal const val LOG_TAG = "BackupRestoreStorage"
 
 /**
- * Storage with backup and restore support. Subclass must implement either [Observable] or
- * [KeyedObservable] interface.
+ * Storage with backup and restore support.
+ *
+ * Subclass MUST
+ * - implement either [Observable] or [KeyedObservable] interface.
+ * - be thread safe, backup/restore happens on Binder thread, while general data read/write
+ *   operations occur on other threads.
  *
  * The storage is identified by a unique string [name] and data set is split into entities
  * ([BackupRestoreEntity]).
  */
+@ThreadSafe
 abstract class BackupRestoreStorage : BackupHelper {
     /**
      * A unique string used to disambiguate the various storages within backup agent.
@@ -68,7 +75,7 @@
     @VisibleForTesting internal var entities: List<BackupRestoreEntity>? = null
 
     /** Entities to back up and restore. */
-    abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity>
+    @BinderThread abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity>
 
     /** Default codec used to encode/decode the entity data. */
     open fun defaultCodec(): BackupCodec = BackupZipCodec.BEST_COMPRESSION
@@ -134,7 +141,11 @@
         Log.i(LOG_TAG, "[$name] Backup end")
     }
 
-    /** Returns if backup is enabled. */
+    /**
+     * Returns if backup is enabled.
+     *
+     * If disabled, [performBackup] will be no-op, all entities backup are skipped.
+     */
     open fun enableBackup(backupContext: BackupContext): Boolean = true
 
     open fun wrapBackupOutputStream(codec: BackupCodec, outputStream: OutputStream): OutputStream {
@@ -172,7 +183,11 @@
     private fun ensureEntities(): List<BackupRestoreEntity> =
         entities ?: createBackupRestoreEntities().also { entities = it }
 
-    /** Returns if restore is enabled. */
+    /**
+     * Returns if restore is enabled.
+     *
+     * If disabled, [restoreEntity] will be no-op, all entities restore are skipped.
+     */
     open fun enableRestore(): Boolean = true
 
     open fun wrapRestoreInputStream(
@@ -188,12 +203,13 @@
     }
 
     final override fun writeNewStateDescription(newState: ParcelFileDescriptor) {
+        if (!enableRestore()) return
         entities = null // clear to reduce memory footprint
         newState.writeAndClearEntityStates()
         onRestoreFinished()
     }
 
-    /** Callbacks when restore finished. */
+    /** Callbacks when entity data are all restored. */
     open fun onRestoreFinished() {}
 
     @VisibleForTesting
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt
index 99998ff..26534ba 100644
--- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt
@@ -248,6 +248,15 @@
     }
 
     @Test
+    fun writeNewStateDescription_restoreDisabled() {
+        val storage = spy(TestStorage().apply { enabled = false })
+        temporaryFolder.newFile().toParcelFileDescriptor(MODE_WRITE_ONLY or MODE_APPEND).use {
+            storage.writeNewStateDescription(it)
+        }
+        verify(storage, never()).onRestoreFinished()
+    }
+
+    @Test
     fun backupAndRestore() {
         val storage = spy(TestStorage(entity1, entity2))
         val backupAgentHelper = BackupAgentHelper()
diff --git a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
index 05507e0..493818b 100644
--- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
+++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
@@ -80,14 +80,15 @@
                 continue;
             }
             final URLSpan urlSpan = (URLSpan) clickable;
-            if (!urlSpan.getURL().startsWith(INTENT_URL_PREFIX)) {
+            final String url = urlSpan.getURL();
+            if (url == null || !url.startsWith(INTENT_URL_PREFIX)) {
                 continue;
             }
             final int start = spannable.getSpanStart(urlSpan);
             final int end = spannable.getSpanEnd(urlSpan);
             spannable.removeSpan(urlSpan);
             try {
-                final Intent intent = Intent.parseUri(urlSpan.getURL(), Intent.URI_INTENT_SCHEME);
+                final Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
                 final ClickableSpan clickableSpan =
                         new ClickableSpan() {
                             @Override
@@ -98,7 +99,7 @@
                         };
                 spannable.setSpan(clickableSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
             } catch (URISyntaxException e) {
-                Log.e(TAG, "Invalid URI " + urlSpan.getURL(), e);
+                Log.e(TAG, "Invalid URI " + url, e);
             }
         }
         title.setText(spannable);
diff --git a/packages/SettingsLib/HelpUtils/res/values-fi/strings.xml b/packages/SettingsLib/HelpUtils/res/values-fi/strings.xml
index de2b100..cef5080 100644
--- a/packages/SettingsLib/HelpUtils/res/values-fi/strings.xml
+++ b/packages/SettingsLib/HelpUtils/res/values-fi/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="help_feedback_label" msgid="7106780063063027882">"Ohje ja palaute"</string>
+    <string name="help_feedback_label" msgid="7106780063063027882">"Ohjeet ja palaute"</string>
 </resources>
diff --git a/packages/SettingsLib/IllustrationPreference/Android.bp b/packages/SettingsLib/IllustrationPreference/Android.bp
index 6407810..c3a91a2 100644
--- a/packages/SettingsLib/IllustrationPreference/Android.bp
+++ b/packages/SettingsLib/IllustrationPreference/Android.bp
@@ -21,6 +21,7 @@
         "SettingsLibColor",
         "androidx.preference_preference",
         "lottie",
+        "settingslib_illustrationpreference_flags_lib",
     ],
 
     sdk_version: "system_current",
@@ -31,3 +32,24 @@
         "com.android.permission",
     ],
 }
+
+aconfig_declarations {
+    name: "settingslib_illustrationpreference_flags",
+    package: "com.android.settingslib.widget.flags",
+    container: "system",
+    srcs: [
+        "aconfig/illustrationpreference.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "settingslib_illustrationpreference_flags_lib",
+    aconfig_declarations: "settingslib_illustrationpreference_flags",
+
+    min_sdk_version: "30",
+
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.permission",
+    ],
+}
diff --git a/packages/SettingsLib/IllustrationPreference/aconfig/illustrationpreference.aconfig b/packages/SettingsLib/IllustrationPreference/aconfig/illustrationpreference.aconfig
new file mode 100644
index 0000000..e566d89
--- /dev/null
+++ b/packages/SettingsLib/IllustrationPreference/aconfig/illustrationpreference.aconfig
@@ -0,0 +1,12 @@
+package: "com.android.settingslib.widget.flags"
+container: "system"
+
+flag {
+    name: "auto_hide_empty_lottie_res"
+    namespace: "android_settings"
+    description: "Hides IllustrationPreference when Lottie resource is an empty file"
+    bug: "337873972"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
\ No newline at end of file
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 815a101..bbf0315 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -39,12 +39,14 @@
 import androidx.preference.PreferenceViewHolder;
 import androidx.vectordrawable.graphics.drawable.Animatable2Compat;
 
+import com.android.settingslib.widget.flags.Flags;
 import com.android.settingslib.widget.preference.illustration.R;
 
 import com.airbnb.lottie.LottieAnimationView;
 import com.airbnb.lottie.LottieDrawable;
 
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.InputStream;
 
 /**
@@ -142,7 +144,7 @@
         illustrationFrame.setLayoutParams(lp);
 
         illustrationView.setCacheComposition(mCacheComposition);
-        handleImageWithAnimation(illustrationView);
+        handleImageWithAnimation(illustrationView, illustrationFrame);
         handleImageFrameMaxHeight(backgroundView, illustrationView);
 
         if (mIsAutoScale) {
@@ -332,7 +334,8 @@
         }
     }
 
-    private void handleImageWithAnimation(LottieAnimationView illustrationView) {
+    private void handleImageWithAnimation(LottieAnimationView illustrationView,
+            ViewGroup container) {
         if (mImageDrawable != null) {
             resetAnimations(illustrationView);
             illustrationView.setImageDrawable(mImageDrawable);
@@ -356,6 +359,25 @@
         }
 
         if (mImageResId > 0) {
+            if (Flags.autoHideEmptyLottieRes()) {
+                // Check if resource is empty
+                try (InputStream is = illustrationView.getResources()
+                        .openRawResource(mImageResId)) {
+                    int check = is.read();
+                    // -1 = end of stream. if first read is end of stream, then file is empty
+                    if (check == -1) {
+                        illustrationView.setVisibility(View.GONE);
+                        container.setVisibility(View.GONE);
+                        return;
+                    }
+                } catch (IOException e) {
+                    Log.w(TAG, "Unable to open Lottie raw resource", e);
+                }
+
+                illustrationView.setVisibility(View.VISIBLE);
+                container.setVisibility(View.VISIBLE);
+            }
+
             resetAnimations(illustrationView);
             illustrationView.setImageResource(mImageResId);
             final Drawable drawable = illustrationView.getDrawable();
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
index 4ced9f2..cece966 100644
--- a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
@@ -16,8 +16,8 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_pressed="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
-    <item android:state_selected="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
-    <item android:state_activated="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
-    <item android:color="@color/settingslib_materialColorSurfaceContainerHighest"/> <!-- not selected -->
+    <item android:state_pressed="true" android:color="@color/settingslib_materialColorSurfaceContainerHigh"/>
+    <item android:state_selected="true" android:color="@color/settingslib_materialColorSurfaceContainerHigh"/>
+    <item android:state_activated="true" android:color="@color/settingslib_materialColorSurfaceContainerHigh"/>
+    <item android:color="@color/settingslib_materialColorSurfaceBright"/> <!-- not selected -->
 </selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
index 285ab73..eba9c2c 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
@@ -19,12 +19,12 @@
     <item
         android:left="?android:attr/listPreferredItemPaddingStart"
         android:right="?android:attr/listPreferredItemPaddingEnd"
-        android:top="1dp">
+        android:top="2dp">
         <shape android:shape="rectangle">
             <solid
                 android:color="@color/settingslib_preference_bg_color" />
             <corners
-                android:radius="?android:attr/dialogCornerRadius" />
+                android:radius="@dimen/settingslib_preference_corner_radius" />
         </shape>
     </item>
 </layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
index e417307..5c60f37 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
@@ -19,15 +19,15 @@
     <item
         android:left="?android:attr/listPreferredItemPaddingStart"
         android:right="?android:attr/listPreferredItemPaddingEnd"
-        android:top="1dp">
+        android:top="2dp">
         <shape android:shape="rectangle">
             <solid
                 android:color="@color/settingslib_preference_bg_color" />
             <corners
-                android:topLeftRadius="0dp"
-                android:bottomLeftRadius="?android:attr/dialogCornerRadius"
-                android:topRightRadius="0dp"
-                android:bottomRightRadius="?android:attr/dialogCornerRadius" />
+                android:topLeftRadius="4dp"
+                android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius"
+                android:topRightRadius="4dp"
+                android:bottomRightRadius="@dimen/settingslib_preference_corner_radius" />
         </shape>
     </item>
 </layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml
new file mode 100644
index 0000000..de64efd
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:left="?android:attr/listPreferredItemPaddingStart"
+        android:right="?android:attr/listPreferredItemPaddingEnd"
+        android:top="2dp">
+        <shape android:shape="rectangle">
+            <solid
+                android:color="@color/settingslib_materialColorSurfaceContainerHigh" />
+            <corners
+                android:topLeftRadius="4dp"
+                android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius"
+                android:topRightRadius="4dp"
+                android:bottomRightRadius="@dimen/settingslib_preference_corner_radius" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
index e964657..dd70f4f 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
@@ -19,12 +19,12 @@
     <item
         android:left="?android:attr/listPreferredItemPaddingStart"
         android:right="?android:attr/listPreferredItemPaddingEnd"
-        android:top="1dp">
+        android:top="2dp">
         <shape android:shape="rectangle">
             <solid
                 android:color="@color/settingslib_preference_bg_color" />
             <corners
-                android:radius="1dp" />
+                android:radius="4dp" />
         </shape>
     </item>
 </layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml
new file mode 100644
index 0000000..fffc6c8
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:left="?android:attr/listPreferredItemPaddingStart"
+        android:right="?android:attr/listPreferredItemPaddingEnd"
+        android:top="2dp">
+        <shape android:shape="rectangle">
+            <solid
+                android:color="@color/settingslib_materialColorSurfaceContainerHigh" />
+            <corners
+                android:radius="4dp" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml
new file mode 100644
index 0000000..f83e3b1
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:left="?android:attr/listPreferredItemPaddingStart"
+        android:right="?android:attr/listPreferredItemPaddingEnd"
+        android:top="2dp">
+        <shape android:shape="rectangle">
+            <solid
+                android:color="@color/settingslib_materialColorSurfaceContainerHigh" />
+            <corners
+                android:radius="@dimen/settingslib_preference_corner_radius" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
index a9d69c2..ab79d18 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
@@ -19,15 +19,15 @@
     <item
         android:left="?android:attr/listPreferredItemPaddingStart"
         android:right="?android:attr/listPreferredItemPaddingEnd"
-        android:top="1dp">
+        android:top="2dp">
         <shape android:shape="rectangle">
             <solid
                 android:color="@color/settingslib_preference_bg_color" />
             <corners
-                android:topLeftRadius="?android:attr/dialogCornerRadius"
-                android:bottomLeftRadius="0dp"
-                android:topRightRadius="?android:attr/dialogCornerRadius"
-                android:bottomRightRadius="0dp" />
+                android:topLeftRadius="@dimen/settingslib_preference_corner_radius"
+                android:bottomLeftRadius="4dp"
+                android:topRightRadius="@dimen/settingslib_preference_corner_radius"
+                android:bottomRightRadius="4dp" />
         </shape>
     </item>
 </layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml
new file mode 100644
index 0000000..112ec73
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:left="?android:attr/listPreferredItemPaddingStart"
+        android:right="?android:attr/listPreferredItemPaddingEnd"
+        android:top="2dp">
+        <shape android:shape="rectangle">
+            <solid
+                android:color="@color/settingslib_materialColorSurfaceContainerHigh" />
+            <corners
+                android:topLeftRadius="@dimen/settingslib_preference_corner_radius"
+                android:bottomLeftRadius="4dp"
+                android:topRightRadius="@dimen/settingslib_preference_corner_radius"
+                android:bottomRightRadius="4dp" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_preference_category_no_title.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_preference_category_no_title.xml
new file mode 100644
index 0000000..eda7daa
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_preference_category_no_title.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:baselineAligned="false"
+    android:layout_marginTop="16dp">
+</LinearLayout>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
index 7c76ea1..94ff02d 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
@@ -37,8 +37,11 @@
     <!-- Material next track off color-->
     <color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color>
 
+    <!-- Dialog text button color. -->
+    <color name="settingslib_dialog_accent">@color/settingslib_materialColorPrimary</color>
+
     <!-- Dialog background color. -->
-    <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color>
+    <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainerHigh</color>
 
     <color name="settingslib_colorSurfaceHeader">@color/settingslib_materialColorSurfaceVariant</color>
 
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
index 2a6499a..8b95016 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
@@ -37,8 +37,11 @@
     <!-- Material next track off color-->
     <color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color>
 
+    <!-- Dialog text button color. -->
+    <color name="settingslib_dialog_accent">@color/settingslib_materialColorPrimary</color>
+
     <!-- Dialog background color. -->
-    <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color>
+    <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainerHigh</color>
 
     <!-- Material next track outline color-->
     <color name="settingslib_track_online_color">@color/settingslib_switch_track_outline_color</color>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml
new file mode 100644
index 0000000..d783956
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<resources>
+    <dimen name="settingslib_preference_corner_radius">20dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 4147813..45667f5 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
 
 allprojects {
     extra["androidTop"] = androidTop
-    extra["jetpackComposeVersion"] = "1.7.0-alpha05"
+    extra["jetpackComposeVersion"] = "1.7.0-alpha08"
 }
 
 subprojects {
@@ -41,7 +41,7 @@
 
             defaultConfig {
                 minSdk = 21
-                targetSdk = 34
+                targetSdk = 35
             }
         }
 
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 6344501..4aa57b3 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -53,17 +53,17 @@
 
 dependencies {
     api(project(":SettingsLibColor"))
-    api("androidx.appcompat:appcompat:1.7.0-alpha03")
+    api("androidx.appcompat:appcompat:1.7.0-beta01")
     api("androidx.slice:slice-builders:1.1.0-alpha02")
     api("androidx.slice:slice-core:1.1.0-alpha02")
     api("androidx.slice:slice-view:1.1.0-alpha02")
-    api("androidx.compose.material3:material3:1.3.0-alpha03")
+    api("androidx.compose.material3:material3:1.3.0-alpha06")
     api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
     api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
     api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
     api("androidx.lifecycle:lifecycle-livedata-ktx")
     api("androidx.lifecycle:lifecycle-runtime-compose")
-    api("androidx.navigation:navigation-compose:2.8.0-alpha05")
+    api("androidx.navigation:navigation-compose:2.8.0-alpha08")
     api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
     api("com.google.android.material:material:1.11.0")
     debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index da1ee77..e867a8f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -21,6 +21,7 @@
 import android.util.Log
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
 import androidx.annotation.VisibleForTesting
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
@@ -30,7 +31,6 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.ui.Modifier
-import androidx.core.view.WindowCompat
 import androidx.navigation.NavBackStackEntry
 import androidx.navigation.NavGraph.Companion.findStartDestination
 import androidx.navigation.NavGraphBuilder
@@ -82,7 +82,7 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         setTheme(R.style.Theme_SpaLib)
         super.onCreate(savedInstanceState)
-        WindowCompat.setDecorFitsSystemWindows(window, false)
+        enableEdgeToEdge()
         spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
 
         setContent {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
index 88ba4b0..1a10bf0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
@@ -27,14 +27,13 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.LinkAnnotation
 import androidx.compose.ui.text.SpanStyle
 import androidx.compose.ui.text.buildAnnotatedString
 import androidx.compose.ui.text.font.FontStyle
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.text.style.TextDecoration
 
-const val URL_SPAN_TAG = "URL_SPAN_TAG"
-
 @Composable
 fun annotatedStringResource(@StringRes id: Int): AnnotatedString {
     val resources = LocalContext.current.resources
@@ -97,12 +96,9 @@
     start: Int,
     end: Int,
 ) {
-    addStyle(
-        SpanStyle(color = urlSpanColor, textDecoration = TextDecoration.Underline),
-        start,
-        end,
+    val url = LinkAnnotation.Url(
+        url = urlSpan.url,
+        style = SpanStyle(color = urlSpanColor, textDecoration = TextDecoration.Underline),
     )
-    if (!urlSpan.url.isNullOrEmpty()) {
-        addStringAnnotation(URL_SPAN_TAG, urlSpan.url, start, end)
-    }
+    addLink(url, start, end)
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index 36cd136..9a344c3 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -35,6 +35,7 @@
 import androidx.compose.foundation.layout.WindowInsetsSides
 import androidx.compose.foundation.layout.only
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
 import androidx.compose.foundation.layout.windowInsetsPadding
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.LocalContentColor
@@ -42,11 +43,11 @@
 import androidx.compose.material3.ProvideTextStyle
 import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
-import androidx.compose.material3.TopAppBarDefaults
 import androidx.compose.material3.TopAppBarScrollBehavior
 import androidx.compose.material3.TopAppBarState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.NonRestartableComposable
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.mutableFloatStateOf
@@ -79,7 +80,12 @@
 import kotlin.math.max
 import kotlin.math.roundToInt
 
-@OptIn(ExperimentalMaterial3Api::class)
+private val windowInsets: WindowInsets
+    @Composable
+    @NonRestartableComposable
+    get() = WindowInsets.safeDrawing
+        .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top)
+
 @Composable
 internal fun CustomizedTopAppBar(
     title: @Composable () -> Unit,
@@ -91,7 +97,7 @@
         titleTextStyle = MaterialTheme.typography.titleMedium,
         navigationIcon = navigationIcon,
         actions = actions,
-        windowInsets = TopAppBarDefaults.windowInsets,
+        windowInsets = windowInsets,
         colors = topAppBarColors(),
     )
 }
@@ -118,7 +124,7 @@
         navigationIcon = navigationIcon,
         actions = actions,
         colors = topAppBarColors(),
-        windowInsets = TopAppBarDefaults.windowInsets,
+        windowInsets = windowInsets,
         pinnedHeight = ContainerHeight,
         scrollBehavior = scrollBehavior,
     )
@@ -336,7 +342,7 @@
         Modifier.draggable(
             orientation = Orientation.Vertical,
             state = rememberDraggableState { delta ->
-                scrollBehavior.state.heightOffset = scrollBehavior.state.heightOffset + delta
+                scrollBehavior.state.heightOffset += delta
             },
             onDragStopped = { velocity ->
                 settleAppBar(
@@ -411,6 +417,7 @@
  * (leading icon), a title (header), and action icons (trailing icons). Note that the navigation and
  * the actions are optional.
  *
+ * @param modifier a [Modifier]
  * @param heightPx the total height this layout is capped to
  * @param navigationIconContentColor the content color that will be applied via a
  * [LocalContentColor] when composing the navigation icon
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
index a49b358..4a7937a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
@@ -22,9 +22,11 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.material3.ExperimentalMaterial3Api
@@ -92,6 +94,7 @@
             )
         },
         containerColor = MaterialTheme.colorScheme.settingsBackground,
+        contentWindowInsets = WindowInsets.safeDrawing,
     ) { paddingValues ->
         Box(
             Modifier
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
index af7a146..4cf741e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
@@ -23,7 +23,9 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Scaffold
@@ -57,6 +59,7 @@
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
         topBar = { SettingsTopAppBar(title, scrollBehavior, actions) },
         containerColor = MaterialTheme.colorScheme.settingsBackground,
+        contentWindowInsets = WindowInsets.safeDrawing,
     ) { paddingValues ->
         Box(Modifier.padding(paddingValues.horizontalValues())) {
             content(paddingValues.verticalValues())
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt
index fc40930..4726dad 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt
@@ -21,7 +21,9 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
@@ -55,7 +57,10 @@
     content: @Composable () -> Unit,
 ) {
     ActivityTitle(title)
-    Scaffold(containerColor = MaterialTheme.colorScheme.settingsBackground) { innerPadding ->
+    Scaffold(
+        containerColor = MaterialTheme.colorScheme.settingsBackground,
+        contentWindowInsets = WindowInsets.safeDrawing,
+    ) { innerPadding ->
         BoxWithConstraints(
             Modifier
                 .padding(innerPadding)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt
index 82ac7e3..f864fa9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt
@@ -17,26 +17,17 @@
 package com.android.settingslib.spa.widget.ui
 
 import androidx.annotation.StringRes
-import androidx.compose.foundation.text.ClickableText
 import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.platform.LocalUriHandler
-import com.android.settingslib.spa.framework.util.URL_SPAN_TAG
 import com.android.settingslib.spa.framework.util.annotatedStringResource
 
 @Composable
 fun AnnotatedText(@StringRes id: Int) {
-    val uriHandler = LocalUriHandler.current
-    val annotatedString = annotatedStringResource(id)
-    ClickableText(
-        text = annotatedString,
+    Text(
+        text = annotatedStringResource(id),
         style = MaterialTheme.typography.bodyMedium.copy(
             color = MaterialTheme.colorScheme.onSurfaceVariant,
         ),
-    ) { offset ->
-        // Gets the url at the clicked position.
-        annotatedString.getStringAnnotations(URL_SPAN_TAG, offset, offset)
-            .firstOrNull()
-            ?.let { uriHandler.openUri(it.item) }
-    }
+    )
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
index 9928355..612f9e5 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
@@ -19,6 +19,7 @@
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.LinkAnnotation
 import androidx.compose.ui.text.SpanStyle
 import androidx.compose.ui.text.font.FontStyle
 import androidx.compose.ui.text.font.FontWeight
@@ -36,18 +37,23 @@
     val composeTestRule = createComposeRule()
 
     @Test
-    fun testAnnotatedStringResource() {
+    fun annotatedStringResource() {
         composeTestRule.setContent {
             val annotatedString =
                 annotatedStringResource(R.string.test_annotated_string_resource)
 
-            val annotations = annotatedString.getStringAnnotations(0, annotatedString.length)
+            val annotations = annotatedString.getLinkAnnotations(0, annotatedString.length)
             assertThat(annotations).containsExactly(
                 AnnotatedString.Range(
-                    item = "https://www.android.com/",
+                    item = LinkAnnotation.Url(
+                        url = "https://www.android.com/",
+                        style = SpanStyle(
+                            color = MaterialTheme.colorScheme.primary,
+                            textDecoration = TextDecoration.Underline,
+                        ),
+                    ),
                     start = 31,
                     end = 35,
-                    tag = URL_SPAN_TAG,
                 )
             )
 
@@ -57,14 +63,6 @@
                     start = 22,
                     end = 26,
                 ),
-                AnnotatedString.Range(
-                    item = SpanStyle(
-                        color = MaterialTheme.colorScheme.primary,
-                        textDecoration = TextDecoration.Underline,
-                    ),
-                    start = 31,
-                    end = 35,
-                ),
             )
         }
     }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
index 6e9bde4..8276e18 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
@@ -29,36 +29,46 @@
 import kotlinx.coroutines.flow.map
 
 interface IAppOpsController {
-    val mode: Flow<Int>
+    val modeFlow: Flow<Int>
     val isAllowed: Flow<Boolean>
-        get() = mode.map { it == MODE_ALLOWED }
+        get() = modeFlow.map { it == MODE_ALLOWED }
 
     fun setAllowed(allowed: Boolean)
 
     @Mode fun getMode(): Int
 }
 
+data class AppOps(
+    val op: Int,
+    val modeForNotAllowed: Int = MODE_ERRORED,
+
+    /**
+     * Use AppOpsManager#setUidMode() instead of AppOpsManager#setMode() when set allowed.
+     *
+     * Security or privacy related app-ops should be set with setUidMode() instead of setMode().
+     */
+    val setModeByUid: Boolean = false,
+)
+
 class AppOpsController(
     context: Context,
     private val app: ApplicationInfo,
-    private val op: Int,
-    private val modeForNotAllowed: Int = MODE_ERRORED,
-    private val setModeByUid: Boolean = false,
+    private val appOps: AppOps,
 ) : IAppOpsController {
     private val appOpsManager = context.appOpsManager
     private val packageManager = context.packageManager
-    override val mode = appOpsManager.opModeFlow(op, app)
+    override val modeFlow = appOpsManager.opModeFlow(appOps.op, app)
 
     override fun setAllowed(allowed: Boolean) {
-        val mode = if (allowed) MODE_ALLOWED else modeForNotAllowed
+        val mode = if (allowed) MODE_ALLOWED else appOps.modeForNotAllowed
 
-        if (setModeByUid) {
-            appOpsManager.setUidMode(op, app.uid, mode)
+        if (appOps.setModeByUid) {
+            appOpsManager.setUidMode(appOps.op, app.uid, mode)
         } else {
-            appOpsManager.setMode(op, app.uid, app.packageName, mode)
+            appOpsManager.setMode(appOps.op, app.uid, app.packageName, mode)
         }
 
-        val permission = AppOpsManager.opToPermission(op)
+        val permission = AppOpsManager.opToPermission(appOps.op)
         if (permission != null) {
             packageManager.updatePermissionFlags(permission, app.packageName,
                     PackageManager.FLAG_PERMISSION_USER_SET,
@@ -67,5 +77,6 @@
         }
     }
 
-    @Mode override fun getMode(): Int = appOpsManager.getOpMode(op, app)
+    @Mode
+    override fun getMode(): Int = appOpsManager.getOpMode(appOps.op, app)
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
index 5db5eae..37b1d73 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
@@ -23,6 +23,7 @@
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settingslib.spa.framework.util.asyncMapItem
 import com.android.settingslib.spa.framework.util.filterItem
+import com.android.settingslib.spaprivileged.model.app.AppOps
 import com.android.settingslib.spaprivileged.model.app.AppOpsController
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.model.app.IAppOpsController
@@ -44,11 +45,11 @@
     private val packageManagers: IPackageManagers = PackageManagers,
 ) : TogglePermissionAppListModel<AppOpPermissionRecord> {
 
-    abstract val appOp: Int
+    abstract val appOps: AppOps
     abstract val permission: String
 
     override val enhancedConfirmationKey: String?
-        get() = AppOpsManager.opToPublicName(appOp)
+        get() = AppOpsManager.opToPublicName(appOps.op)
 
     /**
      * When set, specifies the broader permission who trumps the [permission].
@@ -65,27 +66,12 @@
      */
     open val permissionHasAppOpFlag: Boolean = true
 
-    open val modeForNotAllowed: Int = AppOpsManager.MODE_ERRORED
-
-    /**
-     * Use AppOpsManager#setUidMode() instead of AppOpsManager#setMode() when set allowed.
-     *
-     * Security or privacy related app-ops should be set with setUidMode() instead of setMode().
-     */
-    open val setModeByUid = false
-
     /** These not changeable packages will also be hidden from app list. */
     private val notChangeablePackages =
         setOf("android", "com.android.systemui", context.packageName)
 
     private fun createAppOpsController(app: ApplicationInfo) =
-        AppOpsController(
-            context = context,
-            app = app,
-            op = appOp,
-            setModeByUid = setModeByUid,
-            modeForNotAllowed = modeForNotAllowed,
-        )
+        AppOpsController(context, app, appOps)
 
     private fun createRecord(
         app: ApplicationInfo,
@@ -166,7 +152,7 @@
         return { true }
     }
 
-    val mode = appOpsController.mode.collectAsStateWithLifecycle(initialValue = null)
+    val mode = appOpsController.modeFlow.collectAsStateWithLifecycle(initialValue = null)
     return {
         when (mode.value) {
             null -> null
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
index 91bbd9f..74a7c14 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
@@ -27,16 +27,14 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spaprivileged.framework.common.appOpsManager
 import com.google.common.truth.Truth.assertThat
-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 org.mockito.kotlin.any
-import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
@@ -44,28 +42,18 @@
 class AppOpsControllerTest {
     @get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
 
-    @Spy private val context: Context = ApplicationProvider.getApplicationContext()
+    private val appOpsManager = mock<AppOpsManager>()
 
-    @Mock private lateinit var appOpsManager: AppOpsManager
+    private val packageManager = mock<PackageManager>()
 
-    @Mock private lateinit var packageManager: PackageManager
-
-    @Before
-    fun setUp() {
-        whenever(context.appOpsManager).thenReturn(appOpsManager)
-        whenever(context.packageManager).thenReturn(packageManager)
-        doNothing().whenever(packageManager)
-                .updatePermissionFlags(any(), any(), any(), any(), any())
+    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+        on { appOpsManager } doReturn appOpsManager
+        on { packageManager } doReturn packageManager
     }
 
     @Test
     fun setAllowed_setToTrue() {
-        val controller =
-            AppOpsController(
-                context = context,
-                app = APP,
-                op = OP,
-            )
+        val controller = AppOpsController(context = context, app = APP, appOps = AppOps(OP))
 
         controller.setAllowed(true)
 
@@ -74,12 +62,7 @@
 
     @Test
     fun setAllowed_setToFalse() {
-        val controller =
-            AppOpsController(
-                context = context,
-                app = APP,
-                op = OP,
-            )
+        val controller = AppOpsController(context = context, app = APP, appOps = AppOps(OP))
 
         controller.setAllowed(false)
 
@@ -88,13 +71,11 @@
 
     @Test
     fun setAllowed_setToFalseWithModeForNotAllowed() {
-        val controller =
-            AppOpsController(
-                context = context,
-                app = APP,
-                op = OP,
-                modeForNotAllowed = MODE_IGNORED,
-            )
+        val controller = AppOpsController(
+            context = context,
+            app = APP,
+            appOps = AppOps(op = OP, modeForNotAllowed = MODE_IGNORED),
+        )
 
         controller.setAllowed(false)
 
@@ -103,13 +84,11 @@
 
     @Test
     fun setAllowed_setToTrueByUid() {
-        val controller =
-            AppOpsController(
-                context = context,
-                app = APP,
-                op = OP,
-                setModeByUid = true,
-            )
+        val controller = AppOpsController(
+            context = context,
+            app = APP,
+            appOps = AppOps(op = OP, setModeByUid = true),
+        )
 
         controller.setAllowed(true)
 
@@ -118,13 +97,11 @@
 
     @Test
     fun setAllowed_setToFalseByUid() {
-        val controller =
-            AppOpsController(
-                context = context,
-                app = APP,
-                op = OP,
-                setModeByUid = true,
-            )
+        val controller = AppOpsController(
+            context = context,
+            app = APP,
+            appOps = AppOps(op = OP, setModeByUid = true),
+        )
 
         controller.setAllowed(false)
 
@@ -135,12 +112,7 @@
     fun getMode() {
         whenever(appOpsManager.checkOpNoThrow(OP, APP.uid, APP.packageName))
             .thenReturn(MODE_ALLOWED)
-        val controller =
-            AppOpsController(
-                context = context,
-                app = APP,
-                op = OP,
-            )
+        val controller = AppOpsController(context = context, app = APP, appOps = AppOps(OP))
 
         val mode = controller.getMode()
 
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
index bb25cf3..07ccdd5 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
@@ -25,6 +25,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
 import com.android.settingslib.spaprivileged.framework.common.appOpsManager
+import com.android.settingslib.spaprivileged.model.app.AppOps
 import com.android.settingslib.spaprivileged.model.app.IAppOpsController
 import com.android.settingslib.spaprivileged.model.app.IPackageManagers
 import com.android.settingslib.spaprivileged.test.R
@@ -39,7 +40,6 @@
 import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.spy
-import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
 @RunWith(AndroidJUnit4::class)
@@ -287,16 +287,6 @@
         assertThat(appOpsController.setAllowedCalledWith).isTrue()
     }
 
-    @Test
-    fun setAllowed_setModeByUid() {
-        listModel.setModeByUid = true
-        val record = listModel.transformItem(APP)
-
-        listModel.setAllowed(record = record, newAllowed = true)
-
-        verify(appOpsManager).setUidMode(listModel.appOp, APP.uid, AppOpsManager.MODE_ALLOWED)
-    }
-
     private fun getIsAllowed(record: AppOpPermissionRecord): Boolean? {
         lateinit var isAllowedState: () -> Boolean?
         composeTestRule.setContent { isAllowedState = listModel.isAllowed(record) }
@@ -309,11 +299,9 @@
         override val switchTitleResId = R.string.test_app_op_permission_switch_title
         override val footerResId = R.string.test_app_op_permission_footer
 
-        override val appOp = AppOpsManager.OP_MANAGE_MEDIA
+        override val appOps = AppOps(AppOpsManager.OP_MANAGE_MEDIA)
         override val permission = PERMISSION
         override var broaderPermission: String? = null
-
-        override var setModeByUid = false
     }
 
     private companion object {
@@ -329,7 +317,7 @@
 private class FakeAppOpsController(private val fakeMode: Int) : IAppOpsController {
     var setAllowedCalledWith: Boolean? = null
 
-    override val mode = flowOf(fakeMode)
+    override val modeFlow = flowOf(fakeMode)
 
     override fun setAllowed(allowed: Boolean) {
         setAllowedCalledWith = allowed
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 89f54d9..9c0d29d 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -62,3 +62,10 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+  name: "allow_all_widgets_on_lockscreen_by_default"
+  namespace: "systemui"
+  description: "Allow all widgets on the lock screen by default."
+  bug: "328261690"
+}
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 16dfd6d..ad5337c 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -105,9 +105,9 @@
     <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Regs: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery"</string>
     <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktief"</string>
     <string name="bluetooth_saved_device" msgid="4895871321722311428">"Gestoor"</string>
-    <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktief (net linkerkant)"</string>
-    <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktief (net regterkant)"</string>
-    <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktief (linkerkant en regterkant)"</string>
+    <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktief (net links)"</string>
+    <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktief (net regs)"</string>
+    <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktief (links en regs)"</string>
     <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktief (net media). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery."</string>
     <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktiewe (net media). L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, R: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> battery"</string>
     <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Gekoppel (steun oudiodeling). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery"</string>
@@ -285,9 +285,9 @@
     <string name="oem_unlock_enable_summary" msgid="5857388174390953829">"Laat toe dat die selflaaiprogram ontsluit word"</string>
     <string name="confirm_enable_oem_unlock_title" msgid="8249318129774367535">"Laat OEM-ontsluit toe?"</string>
     <string name="confirm_enable_oem_unlock_text" msgid="854131050791011970">"WAARSKUWING: Toestelbeskermingkenmerke sal nie op hierdie toestel werk terwyl hierdie instelling aangeskakel is nie."</string>
-    <string name="mock_location_app" msgid="6269380172542248304">"Kies skynliggingprogram"</string>
+    <string name="mock_location_app" msgid="6269380172542248304">"Kies skynliggingapp"</string>
     <string name="mock_location_app_not_set" msgid="6972032787262831155">"Geen skynliggingprogram gestel nie"</string>
-    <string name="mock_location_app_set" msgid="4706722469342913843">"Skynliggingprogram: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="mock_location_app_set" msgid="4706722469342913843">"Skynliggingapp: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="debug_networking_category" msgid="6829757985772659599">"Inligtingruiling"</string>
     <string name="wifi_display_certification" msgid="1805579519992520381">"Draadlose skermsertifisering"</string>
     <string name="wifi_verbose_logging" msgid="1785910450009679371">"Aktiveer Wi-Fi-woordryke aanmelding"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 2693ea5..cfa5b14 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -194,7 +194,7 @@
     <string name="launch_defaults_some" msgid="3631650616557252926">"تم ضبط بعض الإعدادات التلقائية"</string>
     <string name="launch_defaults_none" msgid="8049374306261262709">"لم يتم ضبط إعدادات تلقائية"</string>
     <string name="tts_settings" msgid="8130616705989351312">"إعدادات تحويل النص إلى كلام"</string>
-    <string name="tts_settings_title" msgid="7602210956640483039">"الصوت عند تحويل النص إلى كلام"</string>
+    <string name="tts_settings_title" msgid="7602210956640483039">"الرد الصوتي"</string>
     <string name="tts_default_rate_title" msgid="3964187817364304022">"سرعة الكلام"</string>
     <string name="tts_default_rate_summary" msgid="3781937042151716987">"سرعة قول الكلام"</string>
     <string name="tts_default_pitch_title" msgid="6988592215554485479">"درجة الصوت"</string>
@@ -208,7 +208,7 @@
     <string name="tts_install_data_title" msgid="1829942496472751703">"تثبيت البيانات الصوتية"</string>
     <string name="tts_install_data_summary" msgid="3608874324992243851">"تثبيت البيانات الصوتية المطلوبة لتجميع الكلام"</string>
     <string name="tts_engine_security_warning" msgid="3372432853837988146">"ربما يمكن لمحرك اصطناع الحديث جمع كل النص التي سيتم نطقه، بما في ذلك البيانات الشخصية مثل كلمات المرور وأرقام بطاقة الائتمان. يتم إحضار ذلك من المحرك <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g>. هل تريد تفعيل استخدام محرك اصطناع الحديث هذا؟"</string>
-    <string name="tts_engine_network_required" msgid="8722087649733906851">"تتطلب هذه اللغة اتصال شبكة سليمًا لتحويل النص إلى كلام."</string>
+    <string name="tts_engine_network_required" msgid="8722087649733906851">"تتطلب هذه اللغة اتصال شبكة سليمًا لاستخدام ميزة الرد الصوتي."</string>
     <string name="tts_default_sample_string" msgid="6388016028292967973">"هذا مثال لتركيب الكلام"</string>
     <string name="tts_status_title" msgid="8190784181389278640">"حالة اللغة التلقائية"</string>
     <string name="tts_status_ok" msgid="8583076006537547379">"<xliff:g id="LOCALE">%1$s</xliff:g> متوافقة تمامًا"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 2468e06..1c6705e 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"Ніякі"</string>
     <string name="feminine" msgid="1529155595310784757">"Жаночы"</string>
     <string name="masculine" msgid="4653978041013996303">"Мужчынскі"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Абнаўленні сістэмы"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index a82d9df..d6794a2 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"Среден род"</string>
     <string name="feminine" msgid="1529155595310784757">"Женски род"</string>
     <string name="masculine" msgid="4653978041013996303">"Мъжки род"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Системни актуализации"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index b28d896..6fa6e12 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"Střední rod"</string>
     <string name="feminine" msgid="1529155595310784757">"Ženský rod"</string>
     <string name="masculine" msgid="4653978041013996303">"Mužský rod"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Aktualizace systému"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 72b0447..793560d 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"Neutrum"</string>
     <string name="feminine" msgid="1529155595310784757">"Feminin"</string>
     <string name="masculine" msgid="4653978041013996303">"Maskulin"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Systemupdates"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 70979ac..1a0fec5d 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -125,7 +125,7 @@
     <string name="bluetooth_profile_opp" msgid="6692618568149493430">"Transferencia de archivos"</string>
     <string name="bluetooth_profile_hid" msgid="2969922922664315866">"Dispositivo de entrada"</string>
     <string name="bluetooth_profile_pan" msgid="1006235139308318188">"Acceso a Internet"</string>
-    <string name="bluetooth_profile_pbap" msgid="2103406516858653017">"Acceso a contactos e historial de llamadas"</string>
+    <string name="bluetooth_profile_pbap" msgid="2103406516858653017">"Acceso a historial de llamadas y contactos"</string>
     <string name="bluetooth_profile_pbap_summary" msgid="402819589201138227">"Se usará la información para anuncios de llamadas y más"</string>
     <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"Compartir conexión a Internet"</string>
     <string name="bluetooth_profile_map" msgid="8907204701162107271">"Mensajes de texto"</string>
@@ -281,7 +281,7 @@
     <string name="keep_screen_on_summary" msgid="1510731514101925829">"La pantalla nunca quedará inactiva mientras el dispositivo se esté cargando"</string>
     <string name="bt_hci_snoop_log" msgid="7291287955649081448">"Registro de Bluetooth HCI"</string>
     <string name="bt_hci_snoop_log_summary" msgid="6808538971394092284">"Capturar paquetes de Bluetooth (activa/desactiva el Bluetooth después de cambiar esta configuración)"</string>
-    <string name="oem_unlock_enable" msgid="5334869171871566731">"Desbloqueo de OEM"</string>
+    <string name="oem_unlock_enable" msgid="5334869171871566731">"Desbloqueo para OEM"</string>
     <string name="oem_unlock_enable_summary" msgid="5857388174390953829">"Permitir que el cargador de inicio se desbloquee"</string>
     <string name="confirm_enable_oem_unlock_title" msgid="8249318129774367535">"¿Permitir desbloqueo de OEM?"</string>
     <string name="confirm_enable_oem_unlock_text" msgid="854131050791011970">"ADVERTENCIA: Las funciones de protección de dispositivos no funcionarán en este dispositivo mientras esta configuración esté activada."</string>
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"Neutro"</string>
     <string name="feminine" msgid="1529155595310784757">"Femenino"</string>
     <string name="masculine" msgid="4653978041013996303">"Masculino"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Actualizaciones del sistema"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index eb72049..c330b76 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -285,16 +285,16 @@
     <string name="oem_unlock_enable_summary" msgid="5857388174390953829">"Onartu abiarazlea desblokeatzea"</string>
     <string name="confirm_enable_oem_unlock_title" msgid="8249318129774367535">"OEM desblokeoa onartu nahi duzu?"</string>
     <string name="confirm_enable_oem_unlock_text" msgid="854131050791011970">"ABISUA: ezarpen hau aktibatuta dagoen bitartean, gailua babesteko eginbideek ez dute gailu honetan funtzionatuko."</string>
-    <string name="mock_location_app" msgid="6269380172542248304">"Hautatu kokapen faltsuen aplikazioa"</string>
+    <string name="mock_location_app" msgid="6269380172542248304">"Hautatu asmatutako kokapenen aplikazioa"</string>
     <string name="mock_location_app_not_set" msgid="6972032787262831155">"Ez da ezarri kokapen faltsuen aplikaziorik"</string>
-    <string name="mock_location_app_set" msgid="4706722469342913843">"Kokapen faltsuen aplikazioa: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="mock_location_app_set" msgid="4706722469342913843">"Asmatutako kokapenen aplikazioa: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="debug_networking_category" msgid="6829757985772659599">"Sareak"</string>
     <string name="wifi_display_certification" msgid="1805579519992520381">"Hari gabe bistaratzeko ziurtagiria"</string>
     <string name="wifi_verbose_logging" msgid="1785910450009679371">"Gaitu wifi-sareetan saioa hasteko modu xehatua"</string>
     <string name="wifi_scan_throttling" msgid="2985624788509913617">"Wifi-sareen bilaketaren moteltzea"</string>
     <string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Wifi-konexioetan iraunkorrak ez diren MAC helbideak ausaz antolatzea"</string>
     <string name="mobile_data_always_on" msgid="8275958101875563572">"Datu-konexioa beti aktibo"</string>
-    <string name="tethering_hardware_offload" msgid="4116053719006939161">"Konexioa partekatzeko hardwarearen azelerazioa"</string>
+    <string name="tethering_hardware_offload" msgid="4116053719006939161">"Konexioa partekatzeko hardwarearen bizkortzea"</string>
     <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Erakutsi Bluetooth bidezko gailuak izenik gabe"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Desgaitu bolumen absolutua"</string>
     <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gaitu Gabeldorsche"</string>
@@ -339,7 +339,7 @@
     <string name="allow_mock_location_summary" msgid="179780881081354579">"Onartu kokapen faltsuak"</string>
     <string name="debug_view_attributes" msgid="3539609843984208216">"Gaitu ikuspegiaren atributuak ikuskatzeko aukera"</string>
     <string name="mobile_data_always_on_summary" msgid="1112156365594371019">"Mantendu datu-konexioa beti aktibo, baita wifi-konexioa aktibo dagoenean ere (sare batetik bestera bizkor aldatu ahal izateko)."</string>
-    <string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"Erabilgarri badago, erabili konexioa partekatzeko hardwarearen azelerazioa"</string>
+    <string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"Erabilgarri badago, erabili konexioa partekatzeko hardwarearen bizkortzea"</string>
     <string name="adb_warning_title" msgid="7708653449506485728">"USB bidezko arazketa onartu?"</string>
     <string name="adb_warning_message" msgid="8145270656419669221">"USB bidezko arazketa garapen-xedeetarako soilik dago diseinatuta. Erabil ezazu ordenagailuaren eta gailuaren artean datuak kopiatzeko, aplikazioak gailuan jakinarazi gabe instalatzeko eta erregistro-datuak irakurtzeko."</string>
     <string name="adbwifi_warning_title" msgid="727104571653031865">"Hari gabeko arazketa baimendu nahi duzu?"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 72b31e2..1d331fb 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -96,8 +96,8 @@
     <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"متصل (بدون تلفن یا رسانه)، باتری <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string>
     <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"فعال. باتری: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string>
     <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"فعال. باتری چپ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>، باتری راست: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string>
-    <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"فعال. باتری چپ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string>
-    <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"فعال. باتری راست: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string>
+    <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"فعال. چپ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> باتری."</string>
+    <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"فعال. راست: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> باتری."</string>
     <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> شارژ باتری"</string>
     <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"باتری <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
     <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"باتری چپ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>، باتری راست: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string>
@@ -159,7 +159,7 @@
     <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"لغو"</string>
     <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"وقتی وصل باشید، مرتبط‌سازی اجازه دسترسی به مخاطبین و سابقه تماستان را فراهم می‌کند."</string>
     <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"با <xliff:g id="DEVICE_NAME">%1$s</xliff:g> مرتبط‌سازی نشد."</string>
-    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"به‌دلیل پین یا گذرکلید نادرست، مرتبط‌سازی با <xliff:g id="DEVICE_NAME">%1$s</xliff:g> انجام نشد."</string>
+    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"به‌دلیل پین یا گذرکلید نادرست، جفت‌سازی با <xliff:g id="DEVICE_NAME">%1$s</xliff:g> انجام نشد."</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"ارتباط با <xliff:g id="DEVICE_NAME">%1$s</xliff:g> امکان‌پذیر نیست."</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> مرتبط‌سازی را رد کرد."</string>
     <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"رایانه"</string>
@@ -236,7 +236,7 @@
     <string name="category_personal" msgid="6236798763159385225">"شخصی"</string>
     <string name="category_work" msgid="4014193632325996115">"کاری"</string>
     <string name="category_private" msgid="4244892185452788977">"خصوصی"</string>
-    <string name="category_clone" msgid="1554511758987195974">"مشابه‌سازی"</string>
+    <string name="category_clone" msgid="1554511758987195974">"همسانه‌سازی"</string>
     <string name="development_settings_title" msgid="140296922921597393">"گزینه‌های برنامه‌نویسان"</string>
     <string name="development_settings_enable" msgid="4285094651288242183">"فعال کردن گزینه‌های برنامه‌نویس"</string>
     <string name="development_settings_summary" msgid="8718917813868735095">"تنظیم گزینه‌های مربوط به طراحی برنامه"</string>
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"خنثی"</string>
     <string name="feminine" msgid="1529155595310784757">"مؤنث"</string>
     <string name="masculine" msgid="4653978041013996303">"مذکر"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"به‌روزرسانی‌های سیستم"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index dc11d5f..bdf4301 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -159,7 +159,7 @@
     <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"Peru"</string>
     <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"Laiteparin muodostaminen mahdollistaa yhteystietojen ja soittohistorian käyttämisen yhteyden aikana."</string>
     <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"Laiteparin muodostaminen laitteeseen <xliff:g id="DEVICE_NAME">%1$s</xliff:g> epäonnistui."</string>
-    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Laiteparia (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) ei voitu muodostaa, koska PIN tai avain oli väärä."</string>
+    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Laiteparia (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) ei voitu muodostaa, koska PIN tai avainkoodi oli väärä."</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"Ei yhteyttä laitteeseen <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Laite <xliff:g id="DEVICE_NAME">%1$s</xliff:g> torjui laitepariyhteyden."</string>
     <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Tietokone"</string>
@@ -593,7 +593,7 @@
     <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Sisäiset kaiuttimet"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Yhteysvirhe. Sammuta laite ja käynnistä se uudelleen."</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Langallinen äänilaite"</string>
-    <string name="help_label" msgid="3528360748637781274">"Ohje ja palaute"</string>
+    <string name="help_label" msgid="3528360748637781274">"Ohjeet ja palaute"</string>
     <string name="storage_category" msgid="2287342585424631813">"Tallennustila"</string>
     <string name="shared_data_title" msgid="1017034836800864953">"Jaettu data"</string>
     <string name="shared_data_summary" msgid="5516326713822885652">"Katso ja muokkaa jaettua dataa"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 6ba4e83..090c3b9 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -285,7 +285,7 @@
     <string name="oem_unlock_enable_summary" msgid="5857388174390953829">"Autoriser le déverrouillage du fichier d\'amorce"</string>
     <string name="confirm_enable_oem_unlock_title" msgid="8249318129774367535">"Permettre le déverrouillage par le fabricant?"</string>
     <string name="confirm_enable_oem_unlock_text" msgid="854131050791011970">"AVERTISSEMENT : Les fonctionnalités de protection de l\'appareil ne fonctionneront pas sur cet appareil lorsque ce paramètre est activé."</string>
-    <string name="mock_location_app" msgid="6269380172542248304">"Sélectionner l\'application de position fictive"</string>
+    <string name="mock_location_app" msgid="6269380172542248304">"Sélectionner l\'appli de position fictive"</string>
     <string name="mock_location_app_not_set" msgid="6972032787262831155">"Aucune application de position fictive définie"</string>
     <string name="mock_location_app_set" msgid="4706722469342913843">"Application de position fictive : <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="debug_networking_category" msgid="6829757985772659599">"Réseautage"</string>
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"Neutre"</string>
     <string name="feminine" msgid="1529155595310784757">"Féminin"</string>
     <string name="masculine" msgid="4653978041013996303">"Masculin"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Mises à jour du système"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index b463924..21d0bcd 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -159,7 +159,7 @@
     <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"Cancelar"</string>
     <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"A vinculación garante acceso aos teus contactos e ao historial de chamadas ao estar conectado"</string>
     <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"Non se puido vincular con <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
-    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Non se puido vincular con <xliff:g id="DEVICE_NAME">%1$s</xliff:g>, clave de acceso ou PIN incorrectos."</string>
+    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Non se puido vincular con <xliff:g id="DEVICE_NAME">%1$s</xliff:g>; clave de acceso ou PIN incorrectos."</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"Non se pode comunicar con <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Vinculación rexeitada por <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Ordenador"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index f026298..cfb6546 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -126,7 +126,7 @@
     <string name="bluetooth_profile_hid" msgid="2969922922664315866">"इनपुट डिवाइस"</string>
     <string name="bluetooth_profile_pan" msgid="1006235139308318188">"इंटरनेट का ऐक्सेस"</string>
     <string name="bluetooth_profile_pbap" msgid="2103406516858653017">"संपर्क और कॉल इतिहास का ऐक्सेस दें"</string>
-    <string name="bluetooth_profile_pbap_summary" msgid="402819589201138227">"इस जानकारी का इस्तेमाल, कॉल की सूचना देने और दूसरी चीज़ों के लिए किया जाएगा"</string>
+    <string name="bluetooth_profile_pbap_summary" msgid="402819589201138227">"इस जानकारी का इस्तेमाल, कॉल की सूचना देने वगैरह के लिए किया जाएगा"</string>
     <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"इंटरनेट कनेक्शन साझाकरण"</string>
     <string name="bluetooth_profile_map" msgid="8907204701162107271">"लेख संदेश"</string>
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"सिम का ऐक्सेस"</string>
@@ -370,7 +370,7 @@
     <string name="debug_hw_drawing_category" msgid="5830815169336975162">"हार्डवेयर ऐक्सेलरेटेड रेंडरिंग"</string>
     <string name="media_category" msgid="8122076702526144053">"मीडिया"</string>
     <string name="debug_monitoring_category" msgid="1597387133765424994">"निगरानी"</string>
-    <string name="strict_mode" msgid="889864762140862437">"स्ट्रिक्ट मोड चालू किया गया"</string>
+    <string name="strict_mode" msgid="889864762140862437">"स्ट्रिक्ट मोड चालू रखें"</string>
     <string name="strict_mode_summary" msgid="1838248687233554654">"थ्रेड पर लंबा प्रोसेस होने पर स्‍क्रीन फ़्लैश करें"</string>
     <string name="pointer_location" msgid="7516929526199520173">"पॉइंटर की जगह"</string>
     <string name="pointer_location_summary" msgid="957120116989798464">"मौजूदा टच डेटा दिखाने वाला स्‍क्रीन ओवरले"</string>
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"नपुंसक लिंग"</string>
     <string name="feminine" msgid="1529155595310784757">"महिला"</string>
     <string name="masculine" msgid="4653978041013996303">"पुरुष"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"सिस्टम अपडेट"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index e7916a3..0aba7cd 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"Srednji rod"</string>
     <string name="feminine" msgid="1529155595310784757">"Ženski rod"</string>
     <string name="masculine" msgid="4653978041013996303">"Muški rod"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Ažuriranja sustava"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 4c0712c..5e3d779 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -159,7 +159,7 @@
     <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"Չեղարկել"</string>
     <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"Զուգակցում է մուտքի թույլտվությունը դեպի ձեր կոնտակտները և զանգերի պատմությունը, երբ միացված է:"</string>
     <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"Չհաջողվեց զուգակցել <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի հետ:"</string>
-    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Չհաջողվեց զուգակցել <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի հետ սխալ PIN-ի կամ անցաբառի պատճառով:"</string>
+    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Չհաջողվեց զուգակցել <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի հետ՝ սխալ PIN-ի կամ անցաբառի պատճառով:"</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"Հնարավոր չէ կապ հաստատել  <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի հետ:"</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Զուգավորումը մերժվեց <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի կողմից:"</string>
     <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Համակարգիչ"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 81d59f5..c232396 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"ניטרלי"</string>
     <string name="feminine" msgid="1529155595310784757">"נקבה"</string>
     <string name="masculine" msgid="4653978041013996303">"זכר"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"עדכוני מערכת"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 1e0f9b0..4d2f8fc 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"中性"</string>
     <string name="feminine" msgid="1529155595310784757">"女性"</string>
     <string name="masculine" msgid="4653978041013996303">"男性"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"システム アップデート"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 44fd9c8..4e034b4 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -371,7 +371,7 @@
     <string name="media_category" msgid="8122076702526144053">"Mультимeдиа"</string>
     <string name="debug_monitoring_category" msgid="1597387133765424994">"Бақылау"</string>
     <string name="strict_mode" msgid="889864762140862437">"Қатаң режим қосылған"</string>
-    <string name="strict_mode_summary" msgid="1838248687233554654">"Қолданбалар негізгі жолда ұзақ әрекеттерді орындағанда экранды жыпылықтату"</string>
+    <string name="strict_mode_summary" msgid="1838248687233554654">"Қолданбалар ұзақ операцияларды орындағанда экранды жыпылықтату"</string>
     <string name="pointer_location" msgid="7516929526199520173">"Меңзер орны"</string>
     <string name="pointer_location_summary" msgid="957120116989798464">"Экран бетіне түртілген элемент дерегі көрсетіледі"</string>
     <string name="show_touches" msgid="8437666942161289025">"Түрту қимылын көрсету"</string>
@@ -390,7 +390,7 @@
     <string name="simulate_color_space" msgid="1206503300335835151">"Түстер кеңістігінің симуляциясы"</string>
     <string name="enable_opengl_traces_title" msgid="4638773318659125196">"OpenGL трейстерін қосу"</string>
     <string name="usb_audio_disable_routing" msgid="3367656923544254975">"USB-мен аудио жіберуді өшіру"</string>
-    <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"Сыртқы USB аудио құрылғыларына автоматты жіберуді өшіру"</string>
+    <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"Сыртқы USB аудио құрылғыларына автоматты түрде жіберуді өшіру"</string>
     <string name="debug_layout" msgid="1659216803043339741">"Жиектерді көрсету"</string>
     <string name="debug_layout_summary" msgid="8825829038287321978">"Қию шегін, шеттерді, т.б. көрсету"</string>
     <string name="force_rtl_layout_all_locales" msgid="8690762598501599796">"Оңнан солға орналастыру"</string>
@@ -595,7 +595,7 @@
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Сымды аудио құрылғысы"</string>
     <string name="help_label" msgid="3528360748637781274">"Анықтама және пікір"</string>
     <string name="storage_category" msgid="2287342585424631813">"Жад"</string>
-    <string name="shared_data_title" msgid="1017034836800864953">"Бөліскен дерек"</string>
+    <string name="shared_data_title" msgid="1017034836800864953">"Ортақ дерек"</string>
     <string name="shared_data_summary" msgid="5516326713822885652">"Ортақ деректерді көру және өзгерту"</string>
     <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"Бұл пайдаланушымен бөліскен дерек жоқ."</string>
     <string name="shared_data_query_failure_text" msgid="3489828881998773687">"Ортақ деректер алу кезінде қате шықты. Қайталап көріңіз."</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index f950b0c..60f88ea 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"អភេទ"</string>
     <string name="feminine" msgid="1529155595310784757">"ស្រី"</string>
     <string name="masculine" msgid="4653978041013996303">"ប្រុស"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"បច្ចុប្បន្នភាព​ប្រព័ន្ធ"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index a883ceb..d93af73 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"중성"</string>
     <string name="feminine" msgid="1529155595310784757">"여성"</string>
     <string name="masculine" msgid="4653978041013996303">"남성"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"시스템 업데이트"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 247dac1..a6040ef 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -371,7 +371,7 @@
     <string name="media_category" msgid="8122076702526144053">"ມີເດຍ"</string>
     <string name="debug_monitoring_category" msgid="1597387133765424994">"ກຳລັງກວດສອບ"</string>
     <string name="strict_mode" msgid="889864762140862437">"ເປີດໃຊ້ໂໝດເຄັ່ງຄັດ"</string>
-    <string name="strict_mode_summary" msgid="1838248687233554654">"ກະພິບໜ້າຈໍເມື່ອມີແອັບ ເຮັດວຽກດົນເກີນໄປໃນເທຣດຫຼັກ"</string>
+    <string name="strict_mode_summary" msgid="1838248687233554654">"ກະພິບໜ້າຈໍເມື່ອມີແອັບເຮັດວຽກດົນເກີນໄປໃນເທຣດຫຼັກ"</string>
     <string name="pointer_location" msgid="7516929526199520173">"ຕຳແໜ່ງໂຕຊີ້"</string>
     <string name="pointer_location_summary" msgid="957120116989798464">"ການວາງຊ້ອນໜ້າຈໍກຳລັງສະແດງຂໍ້ມູນການສຳຜັດໃນປັດຈຸບັນ"</string>
     <string name="show_touches" msgid="8437666942161289025">"ສະແດງການແຕະ"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 3dd40ee..c07fae3 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -96,8 +96,8 @@
     <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Savienojums izveidots (nav tālrunis vai multivide) (<xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>), akumulatora uzlādes līmenis: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
     <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Aktīvs. Akumulatora uzlādes līmenis: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string>
     <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktīvs. Akumulatora uzlādes līmenis kreisajā austiņā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, labajā austiņā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string>
-    <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktīvs. Kreisā: akumulatora uzlādes līmenis ir <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string>
-    <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktīvs. Labā: akumulatora uzlādes līmenis ir <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string>
+    <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktīvs. Kreisā puse: akumulatora uzlādes līmenis ir <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string>
+    <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktīvs. Labā puse: akumulatora uzlādes līmenis ir <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string>
     <string name="bluetooth_battery_level" msgid="2893696778200201555">"Akumulatora uzlādes līmenis: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
     <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Akumulatora uzlādes līmenis: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
     <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Akumulatora uzlādes līmenis kreisajā austiņā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, labajā austiņā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index f679de5..be8d039 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -94,7 +94,7 @@
     <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Поврзан со <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (без телефон), ниво на батеријата <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
     <string name="bluetooth_connected_no_a2dp_battery_level" msgid="6499078454894324287">"Поврзан со <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (без аудиовизуелни содржини), ниво на батеријата <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
     <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Поврзан со <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (без телефон и аудиовизуелни содржини), ниво на батеријата <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
-    <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Активно. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерија."</string>
+    <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Активно. Батерија: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string>
     <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Активно. Л: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> батерија, Д: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> батерија."</string>
     <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Активно. Л: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерија."</string>
     <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Активно. Д: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерија."</string>
@@ -159,7 +159,7 @@
     <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"Откажи"</string>
     <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"Кога е поврзано, спарувањето одобрува пристап до контактите и историјата на повиците."</string>
     <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"Не може да се спари со <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
-    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Не може да се спари со <xliff:g id="DEVICE_NAME">%1$s</xliff:g> поради погрешен PIN или лозинка."</string>
+    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Не може да се спари со <xliff:g id="DEVICE_NAME">%1$s</xliff:g> поради погрешен PIN или криптографски клуч."</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"Не може да комуницира со <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Спарувањето е одбиено од <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Компјутер"</string>
@@ -371,7 +371,7 @@
     <string name="media_category" msgid="8122076702526144053">"Аудиовизуелни содржини"</string>
     <string name="debug_monitoring_category" msgid="1597387133765424994">"Следење"</string>
     <string name="strict_mode" msgid="889864762140862437">"Овозможен е строг режим"</string>
-    <string name="strict_mode_summary" msgid="1838248687233554654">"Осветли екран при долги операции на главна нишка"</string>
+    <string name="strict_mode_summary" msgid="1838248687233554654">"Трепкај со екранот при долги операции на главна нишка"</string>
     <string name="pointer_location" msgid="7516929526199520173">"Локација на покажувач"</string>
     <string name="pointer_location_summary" msgid="957120116989798464">"Прекривката на екран ги покажува тековните податоци на допир"</string>
     <string name="show_touches" msgid="8437666942161289025">"Прикажувај допири"</string>
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"Среден род"</string>
     <string name="feminine" msgid="1529155595310784757">"Женски род"</string>
     <string name="masculine" msgid="4653978041013996303">"Машки род"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Системски ажурирања"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index 42797d4..66efe8b 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -94,7 +94,7 @@
     <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Холбогдсон (утас байхгүй), батарей <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string>
     <string name="bluetooth_connected_no_a2dp_battery_level" msgid="6499078454894324287">"Холбогдсон (медиа байхгүй), батарей <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string>
     <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Холбогдсон (утас эсвэл медиа байхгүй), батарей <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string>
-    <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Идэвхтэй байна. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батарей."</string>
+    <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Идэвхтэй байна. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> цэнэгтэй."</string>
     <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Идэвхтэй байна. З: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Б: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> батарей."</string>
     <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Идэвхтэй байна. З: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батарей."</string>
     <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Идэвхтэй байна. Б: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батарей."</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 6b7fae7..b3317e4 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -159,7 +159,7 @@
     <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"မလုပ်တော့"</string>
     <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"ချိတ်တွဲမှုက ချိတ်ဆက်ထားလျှင် သင်၏ အဆက်အသွယ်များ နှင့် ခေါ်ဆိုမှု မှတ်တမ်းကို ရယူခွင့် ပြုသည်။"</string>
     <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> နှင့် တွဲချိတ်မရပါ"</string>
-    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"ပင်နံပါတ် (သို့) ဖြတ်သန်းခွင့်ကီး မမှန်ကန်သောကြောင့် <xliff:g id="DEVICE_NAME">%1$s</xliff:g> နှင့် တွဲချိတ်၍မရပါ။"</string>
+    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"ပင်နံပါတ် (သို့) လျှို့ဝှက်ကီး မမှန်ကန်သောကြောင့် <xliff:g id="DEVICE_NAME">%1$s</xliff:g> နှင့် တွဲချိတ်၍မရပါ။"</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>နှင့်ဆက်သွယ်မရပါ"</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>နှင့်တွဲချိတ်ရန် ပယ်ချခံရသည်"</string>
     <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"ကွန်ပျူတာ"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 879752d..8bafef3 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -157,9 +157,9 @@
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"कनेक्ट गर्नुहोस्"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"जोडी"</string>
     <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"रद्द गर्नुहोस्"</string>
-    <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"जब जडान हुन्छ जोडी अनुदानले तपाईँको सम्पर्कहरू पहुँच गर्छ र इतिहास सम्झाउँछ।"</string>
+    <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"जब जडान हुन्छ जोडी अनुदानले तपाईँको कन्ट्याक्टहरू पहुँच गर्छ र इतिहास सम्झाउँछ।"</string>
     <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>सँग जोडा मिलाउन सकेन"</string>
-    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"गलत PIN वा पासकीका कारणले <xliff:g id="DEVICE_NAME">%1$s</xliff:g> सँग कनेक्ट गर्न सकिएन।"</string>
+    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"गलत PIN वा पासकीका कारणले <xliff:g id="DEVICE_NAME">%1$s</xliff:g> मा कनेक्ट गर्न सकिएन।"</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> सँग कुराकानी हुन सक्दैन।"</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> द्वारा जोडा बाँध्ने कार्य अस्वीकृत"</string>
     <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"कम्प्युटर"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 9090efd..d054d9b 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -110,11 +110,11 @@
     <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Ativo (esquerdo e direito)"</string>
     <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Ativo (apenas mídia). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string>
     <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Ativo (apenas mídia). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string>
-    <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Conectado (com suporte ao compartilhamento de áudio). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string>
-    <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Conectado (com suporte ao compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string>
-    <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Conectado (com suporte ao compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string>
-    <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Conectado (com suporte ao compartilhamento de áudio). Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string>
-    <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Conectado (com suporte ao compartilhamento de áudio)"</string>
+    <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Conectado (aceita compartilhamento de áudio). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string>
+    <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Conectado (aceita compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string>
+    <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Conectado (aceita compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string>
+    <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Conectado (aceita compartilhamento de áudio). Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string>
+    <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Conectado (aceita compartilhamento de áudio)"</string>
     <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Ativo (apenas mídia)"</string>
     <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Com suporte ao compartilhamento de áudio"</string>
     <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Ativo (apenas mídia), apenas esquerdo"</string>
@@ -575,7 +575,7 @@
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Alto-falante da base"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Dispositivo externo"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Dispositivo conectado"</string>
-    <string name="media_transfer_this_phone" msgid="7194341457812151531">"Este telefone"</string>
+    <string name="media_transfer_this_phone" msgid="7194341457812151531">"Neste telefone"</string>
     <string name="media_output_status_unknown_error" msgid="5098565887497902222">"Não é possível reproduzir neste dispositivo"</string>
     <string name="media_output_status_require_premium" msgid="8411255800047014822">"Faça upgrade da conta para trocar"</string>
     <string name="media_output_status_not_support_downloads" msgid="4523828729240373315">"Não é possível abrir os downloads aqui"</string>
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"Neutro"</string>
     <string name="feminine" msgid="1529155595310784757">"Feminino"</string>
     <string name="masculine" msgid="4653978041013996303">"Masculino"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Atualizações do sistema"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 6d2d7e6..0bcca15 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"Neutro"</string>
     <string name="feminine" msgid="1529155595310784757">"Feminino"</string>
     <string name="masculine" msgid="4653978041013996303">"Masculino"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Atualizações do sistema"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 9090efd..d054d9b 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -110,11 +110,11 @@
     <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Ativo (esquerdo e direito)"</string>
     <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Ativo (apenas mídia). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string>
     <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Ativo (apenas mídia). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string>
-    <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Conectado (com suporte ao compartilhamento de áudio). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string>
-    <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Conectado (com suporte ao compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string>
-    <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Conectado (com suporte ao compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string>
-    <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Conectado (com suporte ao compartilhamento de áudio). Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string>
-    <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Conectado (com suporte ao compartilhamento de áudio)"</string>
+    <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Conectado (aceita compartilhamento de áudio). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string>
+    <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Conectado (aceita compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string>
+    <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Conectado (aceita compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string>
+    <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Conectado (aceita compartilhamento de áudio). Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string>
+    <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Conectado (aceita compartilhamento de áudio)"</string>
     <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Ativo (apenas mídia)"</string>
     <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Com suporte ao compartilhamento de áudio"</string>
     <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Ativo (apenas mídia), apenas esquerdo"</string>
@@ -575,7 +575,7 @@
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Alto-falante da base"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Dispositivo externo"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Dispositivo conectado"</string>
-    <string name="media_transfer_this_phone" msgid="7194341457812151531">"Este telefone"</string>
+    <string name="media_transfer_this_phone" msgid="7194341457812151531">"Neste telefone"</string>
     <string name="media_output_status_unknown_error" msgid="5098565887497902222">"Não é possível reproduzir neste dispositivo"</string>
     <string name="media_output_status_require_premium" msgid="8411255800047014822">"Faça upgrade da conta para trocar"</string>
     <string name="media_output_status_not_support_downloads" msgid="4523828729240373315">"Não é possível abrir os downloads aqui"</string>
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"Neutro"</string>
     <string name="feminine" msgid="1529155595310784757">"Feminino"</string>
     <string name="masculine" msgid="4653978041013996303">"Masculino"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Atualizações do sistema"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index c02e65b..aaf648e 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -94,7 +94,7 @@
     <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Подключено (кроме звонков), уровень заряда батареи: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string>
     <string name="bluetooth_connected_no_a2dp_battery_level" msgid="6499078454894324287">"Подключено (кроме аудио), уровень заряда батареи: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string>
     <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Подключено (кроме звонков и аудио), уровень заряда батареи: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string>
-    <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Используется, заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string>
+    <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Используется, заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
     <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Используется, заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> (Л), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> (П)."</string>
     <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Используется, заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (Л)"</string>
     <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Используется, заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (П)"</string>
@@ -159,7 +159,7 @@
     <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"Отмена"</string>
     <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"Установление соединения обеспечивает доступ к вашим контактам и журналу звонков при подключении."</string>
     <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"Не удалось подключиться к устройству \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\"."</string>
-    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"\"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\" не подключается: неверный PIN-код или пароль."</string>
+    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Устройство \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\" не подключено: неверный PIN-код или пароль."</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"Не удается установить соединение с устройством \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\"."</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> не разрешает подключение."</string>
     <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Компьютер"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 3db9f40..ab423b5 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"නපුංසක"</string>
     <string name="feminine" msgid="1529155595310784757">"ස්ත්‍රී"</string>
     <string name="masculine" msgid="4653978041013996303">"පුරුෂ"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"පද්ධති යාවත්කාලීන"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 114adfd..cc32432 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"Neutrum"</string>
     <string name="feminine" msgid="1529155595310784757">"Femin­informer"</string>
     <string name="masculine" msgid="4653978041013996303">"Maskulinformer"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Systemuppdateringar"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 56b28d8..0eac15c 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"Isiyobainika"</string>
     <string name="feminine" msgid="1529155595310784757">"Jinsia ya kike"</string>
     <string name="masculine" msgid="4653978041013996303">"Jinsia ya kiume"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Masasisho ya Mfumo"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 47c5993..7c87eb7 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"அஃறிணை"</string>
     <string name="feminine" msgid="1529155595310784757">"பெண்"</string>
     <string name="masculine" msgid="4653978041013996303">"ஆண்"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"சிஸ்டம் புதுப்பிப்புகள்"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 766f9b2..689ea6f1 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -159,7 +159,7 @@
     <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"రద్దు చేయండి"</string>
     <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"పెయిర్ చేయడం వలన కనెక్ట్ చేయబడినప్పుడు మీ కాంటాక్ట్‌లకు అలాగే కాల్ హిస్టరీకి యాక్సెస్‌ను మంజూరు చేస్తుంది."</string>
     <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>తో జత చేయడం సాధ్యపడలేదు."</string>
-    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"PIN లేదా పాస్‌కీ చెల్లని కారణంగా <xliff:g id="DEVICE_NAME">%1$s</xliff:g>తో పెయిర్ చేయడం సాధ్యపడలేదు."</string>
+    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"PIN గానీ, పాస్‌కీ గానీ చెల్లని కారణంగా <xliff:g id="DEVICE_NAME">%1$s</xliff:g>తో పెయిర్ చేయడం సాధ్యపడలేదు."</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>తో కమ్యూనికేట్ చేయడం సాధ్యపడదు."</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> జత చేయడాన్ని తిరస్కరించింది."</string>
     <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"కంప్యూటర్"</string>
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"తటస్థం"</string>
     <string name="feminine" msgid="1529155595310784757">"స్త్రీ"</string>
     <string name="masculine" msgid="4653978041013996303">"పురుషుడు"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"సిస్టమ్ అప్‌డేట్‌లు"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 79d8fe1..1899914 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"ไม่มีเพศ"</string>
     <string name="feminine" msgid="1529155595310784757">"เพศหญิง"</string>
     <string name="masculine" msgid="4653978041013996303">"เพศชาย"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"การอัปเดตระบบ"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 6719bb0..144c20b 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"Cinsiyetsiz"</string>
     <string name="feminine" msgid="1529155595310784757">"Kadın"</string>
     <string name="masculine" msgid="4653978041013996303">"Erkek"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Sistem Güncellemeleri"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 10b47d9..ecfa2c9 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -721,6 +721,5 @@
     <string name="neuter" msgid="2075249330106127310">"Середній рід"</string>
     <string name="feminine" msgid="1529155595310784757">"Жіночий рід"</string>
     <string name="masculine" msgid="4653978041013996303">"Чоловічий рід"</string>
-    <!-- no translation found for system_update_settings_list_item_title (6618098383615432484) -->
-    <skip />
+    <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Оновлення системи"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 4286c81..ed0b1df 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -159,7 +159,7 @@
     <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"منسوخ کریں"</string>
     <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"منسلک ہونے پر جوڑا بنانے سے آپ کے رابطوں اور کال کی سرگزشت تک رسائی حاصل ہو جاتی ہے۔"</string>
     <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> کے ساتھ جوڑا نہیں بنا سکا۔"</string>
-    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"‏غلط PIN یا پاس کلید کی وجہ سے <xliff:g id="DEVICE_NAME">%1$s</xliff:g> کے ساتھ جوڑا نہیں بنا سکا۔"</string>
+    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"‏غلط PIN یا پاس کلید کی وجہ سے <xliff:g id="DEVICE_NAME">%1$s</xliff:g> کے ساتھ جوڑا نہیں بنایا جا سکا۔"</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> کے ساتھ مواصلت نہیں ہو سکتی۔"</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> نے جوڑا بنانے کو مسترد کر دیا۔"</string>
     <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"کمپیوٹر"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 56282ec..25ca0c2 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -706,9 +706,9 @@
     <string name="physical_keyboard_title" msgid="4811935435315835220">"实体键盘"</string>
     <string name="keyboard_layout_dialog_title" msgid="3927180147005616290">"选择键盘布局"</string>
     <string name="keyboard_layout_default_label" msgid="1997292217218546957">"默认"</string>
-    <string name="turn_screen_on_title" msgid="3266937298097573424">"开启屏幕"</string>
+    <string name="turn_screen_on_title" msgid="3266937298097573424">"唤醒屏幕"</string>
     <string name="allow_turn_screen_on" msgid="6194845766392742639">"允许开启屏幕"</string>
-    <string name="allow_turn_screen_on_description" msgid="43834403291575164">"允许应用开启屏幕。如获授权,该应用便可在您未明确表达意愿的情况下随时开启屏幕。"</string>
+    <string name="allow_turn_screen_on_description" msgid="43834403291575164">"允许应用唤醒屏幕。如获授权,该应用便可在您未明确表达意愿的情况下随时唤醒屏幕。"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="5392738488989777074">"要停止广播“<xliff:g id="APP_NAME">%1$s</xliff:g>”的内容吗?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"如果广播“<xliff:g id="SWITCHAPP">%1$s</xliff:g>”的内容或更改输出来源,当前的广播就会停止"</string>
     <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"广播“<xliff:g id="SWITCHAPP">%1$s</xliff:g>”的内容"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index adbfc72..363045e 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -214,6 +214,10 @@
     <string name="bluetooth_battery_level_untethered_left">Left: <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g> battery</string>
     <!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing remote device battery level for the right part of the untethered headset. [CHAR LIMIT=NONE] -->
     <string name="bluetooth_battery_level_untethered_right">Right: <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g> battery</string>
+    <!-- Connected devices settings. Message when Bluetooth is connected, showing remote device battery level for the left part of the untethered headset. [CHAR LIMIT=NONE] -->
+    <string name="tv_bluetooth_battery_level_untethered_left">Left <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g></string>
+    <!-- Connected devices settings. Message when Bluetooth is connected, showing remote device battery level for the right part of the untethered headset. [CHAR LIMIT=NONE] -->
+    <string name="tv_bluetooth_battery_level_untethered_right">Right <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g></string>
     <!-- Connected devices settings. Message when Bluetooth is connected and active but no battery information, showing remote device status. [CHAR LIMIT=NONE] -->
     <string name="bluetooth_active_no_battery_level">Active</string>
     <!-- Connected devices settings. Message shown when bluetooth device is disconnected but is a known, previously connected device [CHAR LIMIT=NONE] -->
@@ -1712,7 +1716,7 @@
     <string name="keyboard_layout_default_label">Default</string>
 
     <!-- Special access > Title for managing turn screen on settings. [CHAR LIMIT=50] -->
-    <string name="turn_screen_on_title">Turn screen on</string>
+    <string name="turn_screen_on_title">Screen turn-on control</string>
     <!-- Label for a setting which controls whether an app can turn the screen on [CHAR LIMIT=45] -->
     <string name="allow_turn_screen_on">Allow turning the screen on</string>
     <!-- Description for a setting which controls whether an app can turn the screen on [CHAR LIMIT=NONE] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
index 57bde56..c365142 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
@@ -128,7 +128,7 @@
         final long now = mClock.millis();
         final UserManager um = mContext.getSystemService(UserManager.class);
         final List<UserHandle> profiles = um.getUserProfiles();
-        ArrayMap<UserHandle, Boolean> shouldIncludeAppsByUsers = new ArrayMap<>();
+        ArrayMap<UserHandle, Boolean> shouldHideAppsByUsers = new ArrayMap<>();
 
         for (int i = 0; i < appOpsCount; ++i) {
             AppOpsManager.PackageOps ops = appOps.get(i);
@@ -136,13 +136,13 @@
             int uid = ops.getUid();
             UserHandle user = UserHandle.getUserHandleForUid(uid);
 
-            if (!shouldIncludeAppsByUsers.containsKey(user)) {
-                shouldIncludeAppsByUsers.put(user, shouldHideUser(um, user));
+            if (!shouldHideAppsByUsers.containsKey(user)) {
+                shouldHideAppsByUsers.put(user, shouldHideUser(um, user));
             }
 
             // Don't show apps belonging to background users except for profiles that shouldn't
             // be shown in quiet mode.
-            if (!profiles.contains(user) || !shouldIncludeAppsByUsers.get(user)) {
+            if (!profiles.contains(user) || shouldHideAppsByUsers.get(user)) {
                 continue;
             }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 3dffb27..8917412 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -10,8 +10,10 @@
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothStatusCodes;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -37,12 +39,9 @@
 import com.android.settingslib.widget.AdaptiveIcon;
 import com.android.settingslib.widget.AdaptiveOutlineDrawable;
 
-import com.google.common.collect.ImmutableSet;
-
 import java.io.IOException;
 import java.util.List;
 import java.util.Locale;
-import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -56,8 +55,6 @@
     public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled";
     private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
     private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH";
-    private static final Set<String> EXCLUSIVE_MANAGERS =
-            ImmutableSet.of("com.google.android.gms.dck");
 
     private static ErrorListener sErrorListener;
 
@@ -740,14 +737,13 @@
 
     /**
      * Returns the BluetoothDevice's exclusive manager ({@link
-     * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the given
-     * set, otherwise null.
+     * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists, otherwise null.
      */
     @Nullable
-    private static String getAllowedExclusiveManager(BluetoothDevice bluetoothDevice) {
-        byte[] exclusiveManagerNameBytes =
+    private static String getExclusiveManager(BluetoothDevice bluetoothDevice) {
+        byte[] exclusiveManagerBytes =
                 bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER);
-        if (exclusiveManagerNameBytes == null) {
+        if (exclusiveManagerBytes == null) {
             Log.d(
                     TAG,
                     "Bluetooth device "
@@ -755,47 +751,46 @@
                             + " doesn't have exclusive manager");
             return null;
         }
-        String exclusiveManagerName = new String(exclusiveManagerNameBytes);
-        return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName : null;
+        return new String(exclusiveManagerBytes);
     }
 
-    /** Checks if given package is installed */
-    private static boolean isPackageInstalled(Context context, String packageName) {
+    /** Checks if given package is installed and enabled */
+    private static boolean isPackageInstalledAndEnabled(Context context, String packageName) {
         PackageManager packageManager = context.getPackageManager();
         try {
-            packageManager.getPackageInfo(packageName, 0);
-            return true;
+            ApplicationInfo appInfo = packageManager.getApplicationInfo(packageName, 0);
+            return appInfo.enabled;
         } catch (PackageManager.NameNotFoundException e) {
-            Log.d(TAG, "Package " + packageName + " is not installed");
+            Log.d(TAG, "Package " + packageName + " is not installed/enabled");
         }
         return false;
     }
 
     /**
      * A BluetoothDevice is exclusively managed if 1) it has field {@link
-     * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. 2) the exclusive manager app name is
-     * in the allowlist. 3) the exclusive manager app is installed.
+     * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. 2) the exclusive manager app is
+     * installed and enabled.
      */
     public static boolean isExclusivelyManagedBluetoothDevice(
             @NonNull Context context, @NonNull BluetoothDevice bluetoothDevice) {
-        String exclusiveManagerName = getAllowedExclusiveManager(bluetoothDevice);
+        String exclusiveManagerName = getExclusiveManager(bluetoothDevice);
         if (exclusiveManagerName == null) {
             return false;
         }
-        if (!isPackageInstalled(context, exclusiveManagerName)) {
+
+        ComponentName exclusiveManagerComponent =
+                ComponentName.unflattenFromString(exclusiveManagerName);
+        String exclusiveManagerPackage = exclusiveManagerComponent != null
+                ? exclusiveManagerComponent.getPackageName() : exclusiveManagerName;
+
+        if (!isPackageInstalledAndEnabled(context, exclusiveManagerPackage)) {
             return false;
         } else {
-            Log.d(TAG, "Found exclusively managed app " + exclusiveManagerName);
+            Log.d(TAG, "Found exclusively managed app " + exclusiveManagerPackage);
             return true;
         }
     }
 
-    /** Return the allowlist for exclusive manager names. */
-    @NonNull
-    public static Set<String> getExclusiveManagers() {
-        return EXCLUSIVE_MANAGERS;
-    }
-
     /**
      * Get CSIP group id for {@link CachedBluetoothDevice}.
      *
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index a7b7da5..0fec61c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -68,6 +68,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -649,6 +650,9 @@
         for (CachedBluetoothDevice cbd : mMemberDevices) {
             cbd.setName(name);
         }
+        if (mSubDevice != null) {
+            mSubDevice.setName(name);
+        }
     }
 
     /**
@@ -1439,14 +1443,8 @@
         int stringRes = R.string.bluetooth_pairing;
         //when profile is connected, information would be available
         if (profileConnected) {
-            // Update Meta data for connected device
-            if (BluetoothUtils.getBooleanMetaData(
-                    mDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
-                leftBattery = BluetoothUtils.getIntMetaData(mDevice,
-                        BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY);
-                rightBattery = BluetoothUtils.getIntMetaData(mDevice,
-                        BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY);
-            }
+            leftBattery = getLeftBatteryLevel();
+            rightBattery = getRightBatteryLevel();
 
             // Set default string with battery level in device connected situation.
             if (isTwsBatteryAvailable(leftBattery, rightBattery)) {
@@ -1482,7 +1480,7 @@
                 boolean isActiveLeAudioHearingAid = mIsActiveDeviceLeAudio
                         && isConnectedHapClientDevice();
                 if (isActiveAshaHearingAid || isActiveLeAudioHearingAid) {
-                    return getHearingDeviceSummary(leftBattery, rightBattery, shortSummary);
+                    stringRes = getHearingDeviceSummaryRes(leftBattery, rightBattery, shortSummary);
                 }
             }
         }
@@ -1495,6 +1493,8 @@
         boolean summaryIncludesBatteryLevel = stringRes == R.string.bluetooth_battery_level
                 || stringRes == R.string.bluetooth_active_battery_level
                 || stringRes == R.string.bluetooth_active_battery_level_untethered
+                || stringRes == R.string.bluetooth_active_battery_level_untethered_left
+                || stringRes == R.string.bluetooth_active_battery_level_untethered_right
                 || stringRes == R.string.bluetooth_battery_level_untethered;
         if (isTvSummary && summaryIncludesBatteryLevel && Flags.enableTvMediaOutputDialog()) {
             return getTvBatterySummary(
@@ -1507,6 +1507,14 @@
         if (isTwsBatteryAvailable(leftBattery, rightBattery)) {
             return mContext.getString(stringRes, Utils.formatPercentage(leftBattery),
                     Utils.formatPercentage(rightBattery));
+        } else if (leftBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN
+                && !BluetoothUtils.getBooleanMetaData(mDevice,
+                BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
+            return mContext.getString(stringRes, Utils.formatPercentage(leftBattery));
+        } else if (rightBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN
+                && !BluetoothUtils.getBooleanMetaData(mDevice,
+                BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
+            return mContext.getString(stringRes, Utils.formatPercentage(rightBattery));
         } else {
             return mContext.getString(stringRes, batteryLevelPercentageString);
         }
@@ -1524,7 +1532,7 @@
             // the left.
             if (leftBattery >= 0) {
                 String left = res.getString(
-                        R.string.bluetooth_battery_level_untethered_left,
+                        R.string.tv_bluetooth_battery_level_untethered_left,
                         Utils.formatPercentage(leftBattery));
                 addBatterySpan(spannableBuilder, left, isBatteryLow(leftBattery,
                                 BluetoothDevice.METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD),
@@ -1535,7 +1543,7 @@
                     spannableBuilder.append(" ");
                 }
                 String right = res.getString(
-                        R.string.bluetooth_battery_level_untethered_right,
+                        R.string.tv_bluetooth_battery_level_untethered_right,
                         Utils.formatPercentage(rightBattery));
                 addBatterySpan(spannableBuilder, right, isBatteryLow(rightBattery,
                                 BluetoothDevice.METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD),
@@ -1550,60 +1558,34 @@
         return spannableBuilder;
     }
 
-    private CharSequence getHearingDeviceSummary(int leftBattery, int rightBattery,
+    private int getHearingDeviceSummaryRes(int leftBattery, int rightBattery,
             boolean shortSummary) {
+        boolean isLeftDeviceConnected = getConnectedHearingAidSide(
+                HearingAidInfo.DeviceSide.SIDE_LEFT).isPresent();
+        boolean isRightDeviceConnected = getConnectedHearingAidSide(
+                HearingAidInfo.DeviceSide.SIDE_RIGHT).isPresent();
+        boolean shouldShowLeftBattery =
+                !shortSummary && (leftBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+        boolean shouldShowRightBattery =
+                !shortSummary && (rightBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
 
-        CachedBluetoothDevice memberDevice = getMemberDevice().stream().filter(
-                CachedBluetoothDevice::isConnected).findFirst().orElse(null);
-        if (memberDevice == null && mSubDevice != null && mSubDevice.isConnected()) {
-            memberDevice = mSubDevice;
+        if (isLeftDeviceConnected && isRightDeviceConnected) {
+            return (shouldShowLeftBattery && shouldShowRightBattery)
+                    ? R.string.bluetooth_active_battery_level_untethered
+                    : R.string.bluetooth_hearing_aid_left_and_right_active;
+        }
+        if (isLeftDeviceConnected) {
+            return shouldShowLeftBattery
+                    ? R.string.bluetooth_active_battery_level_untethered_left
+                    : R.string.bluetooth_hearing_aid_left_active;
+        }
+        if (isRightDeviceConnected) {
+            return shouldShowRightBattery
+                    ? R.string.bluetooth_active_battery_level_untethered_right
+                    : R.string.bluetooth_hearing_aid_right_active;
         }
 
-        CachedBluetoothDevice leftDevice = null;
-        CachedBluetoothDevice rightDevice = null;
-        final int deviceSide = getDeviceSide();
-        if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT) {
-            leftDevice = this;
-            rightDevice = memberDevice;
-        } else if (deviceSide == HearingAidInfo.DeviceSide.SIDE_RIGHT) {
-            leftDevice = memberDevice;
-            rightDevice = this;
-        } else if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT) {
-            leftDevice = this;
-            rightDevice = this;
-        }
-
-        if (leftBattery < 0 && leftDevice != null) {
-            leftBattery = leftDevice.getBatteryLevel();
-        }
-        if (rightBattery < 0 && rightDevice != null) {
-            rightBattery = rightDevice.getBatteryLevel();
-        }
-
-        if (leftDevice != null && rightDevice != null) {
-            if (leftBattery >= 0 && rightBattery >= 0 && !shortSummary) {
-                return mContext.getString(R.string.bluetooth_active_battery_level_untethered,
-                        Utils.formatPercentage(leftBattery), Utils.formatPercentage(rightBattery));
-            } else {
-                return mContext.getString(R.string.bluetooth_hearing_aid_left_and_right_active);
-            }
-        } else if (leftDevice != null) {
-            if (leftBattery >= 0 && !shortSummary) {
-                return mContext.getString(R.string.bluetooth_active_battery_level_untethered_left,
-                        Utils.formatPercentage(leftBattery));
-            } else {
-                return mContext.getString(R.string.bluetooth_hearing_aid_left_active);
-            }
-        } else if (rightDevice != null) {
-            if (rightBattery >= 0 && !shortSummary) {
-                return mContext.getString(R.string.bluetooth_active_battery_level_untethered_right,
-                        Utils.formatPercentage(rightBattery));
-            } else {
-                return mContext.getString(R.string.bluetooth_hearing_aid_right_active);
-            }
-        }
-
-        return mContext.getString(R.string.bluetooth_active_no_battery_level);
+        return R.string.bluetooth_active_no_battery_level;
     }
 
     private void addBatterySpan(SpannableStringBuilder builder,
@@ -1629,6 +1611,56 @@
         return leftBattery >= 0 && rightBattery >= 0;
     }
 
+    private Optional<CachedBluetoothDevice> getConnectedHearingAidSide(
+            @HearingAidInfo.DeviceSide int side) {
+        return Stream.concat(Stream.of(this, mSubDevice), mMemberDevices.stream())
+                .filter(Objects::nonNull)
+                .filter(device -> device.getDeviceSide() == side
+                        || device.getDeviceSide() == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT)
+                .filter(device -> device.getDevice().isConnected())
+                // For hearing aids, we should expect only one device assign to one side, but if
+                // it happens, we don't care which one.
+                .findAny();
+    }
+
+    private int getLeftBatteryLevel() {
+        int leftBattery = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
+        if (BluetoothUtils.getBooleanMetaData(mDevice,
+                BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
+            leftBattery = BluetoothUtils.getIntMetaData(mDevice,
+                    BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY);
+        }
+
+        // Retrieve hearing aids (ASHA, HAP) individual side battery level
+        if (leftBattery == BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+            leftBattery = getConnectedHearingAidSide(HearingAidInfo.DeviceSide.SIDE_LEFT)
+                    .map(CachedBluetoothDevice::getBatteryLevel)
+                    .filter(batteryLevel -> batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
+                    .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+        }
+
+        return leftBattery;
+    }
+
+    private int getRightBatteryLevel() {
+        int rightBattery = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
+        if (BluetoothUtils.getBooleanMetaData(
+                mDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
+            rightBattery = BluetoothUtils.getIntMetaData(mDevice,
+                    BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY);
+        }
+
+        // Retrieve hearing aids (ASHA, HAP) individual side battery level
+        if (rightBattery == BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+            rightBattery = getConnectedHearingAidSide(HearingAidInfo.DeviceSide.SIDE_RIGHT)
+                    .map(CachedBluetoothDevice::getBatteryLevel)
+                    .filter(batteryLevel -> batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
+                    .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+        }
+
+        return rightBattery;
+    }
+
     private boolean isProfileConnectedFail() {
         Log.d(TAG, "anonymizedAddress=" + mDevice.getAnonymizedAddress()
                 + " mIsA2dpProfileConnectedFail=" + mIsA2dpProfileConnectedFail
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index 9bf42f9..ed964a9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -100,6 +100,7 @@
             // device.
             if (hearingAidDevice != null) {
                 hearingAidDevice.setSubDevice(newDevice);
+                newDevice.setName(hearingAidDevice.getName());
                 return true;
             }
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java
index 8e3df8b..2de2174 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java
@@ -48,17 +48,15 @@
     private static final String BT_HEARING_AIDS_PAIRED_HISTORY = "bt_hearing_aids_paired_history";
     private static final String BT_HEARING_AIDS_CONNECTED_HISTORY =
             "bt_hearing_aids_connected_history";
-    private static final String BT_HEARING_DEVICES_PAIRED_HISTORY =
+    private static final String BT_HEARABLE_DEVICES_PAIRED_HISTORY =
             "bt_hearing_devices_paired_history";
-    private static final String BT_HEARING_DEVICES_CONNECTED_HISTORY =
+    private static final String BT_HEARABLE_DEVICES_CONNECTED_HISTORY =
             "bt_hearing_devices_connected_history";
-    private static final String BT_HEARING_USER_CATEGORY = "bt_hearing_user_category";
-
     private static final String HISTORY_RECORD_DELIMITER = ",";
     static final String CATEGORY_HEARING_AIDS = "A11yHearingAidsUser";
     static final String CATEGORY_NEW_HEARING_AIDS = "A11yNewHearingAidsUser";
-    static final String CATEGORY_HEARING_DEVICES = "A11yHearingDevicesUser";
-    static final String CATEGORY_NEW_HEARING_DEVICES = "A11yNewHearingDevicesUser";
+    static final String CATEGORY_HEARABLE_DEVICES = "A11yHearingDevicesUser";
+    static final String CATEGORY_NEW_HEARABLE_DEVICES = "A11yNewHearingDevicesUser";
 
     static final int PAIRED_HISTORY_EXPIRED_DAY = 30;
     static final int CONNECTED_HISTORY_EXPIRED_DAY = 7;
@@ -73,14 +71,14 @@
             HistoryType.TYPE_UNKNOWN,
             HistoryType.TYPE_HEARING_AIDS_PAIRED,
             HistoryType.TYPE_HEARING_AIDS_CONNECTED,
-            HistoryType.TYPE_HEARING_DEVICES_PAIRED,
-            HistoryType.TYPE_HEARING_DEVICES_CONNECTED})
+            HistoryType.TYPE_HEARABLE_DEVICES_PAIRED,
+            HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED})
     public @interface HistoryType {
         int TYPE_UNKNOWN = -1;
         int TYPE_HEARING_AIDS_PAIRED = 0;
         int TYPE_HEARING_AIDS_CONNECTED = 1;
-        int TYPE_HEARING_DEVICES_PAIRED = 2;
-        int TYPE_HEARING_DEVICES_CONNECTED = 3;
+        int TYPE_HEARABLE_DEVICES_PAIRED = 2;
+        int TYPE_HEARABLE_DEVICES_CONNECTED = 3;
     }
 
     private static final HashMap<String, Integer> sDeviceAddressToBondEntryMap = new HashMap<>();
@@ -127,8 +125,8 @@
     }
 
     /**
-     * Updates corresponding history if we found the device is a hearing device after profile state
-     * changed.
+     * Updates corresponding history if we found the device is a hearing related device after
+     * profile state changed.
      *
      * @param context the request context
      * @param cachedDevice the remote device
@@ -148,7 +146,7 @@
             } else if (cachedDevice.getProfiles().stream().anyMatch(
                     p -> (p instanceof A2dpSinkProfile || p instanceof HeadsetProfile))) {
                 HearingAidStatsLogUtils.addCurrentTimeToHistory(context,
-                        HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_PAIRED);
+                        HearingAidStatsLogUtils.HistoryType.TYPE_HEARABLE_DEVICES_PAIRED);
             }
             removeFromJustBonded(cachedDevice.getAddress());
         }
@@ -161,7 +159,7 @@
                         HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_CONNECTED);
             } else if (profile instanceof A2dpSinkProfile || profile instanceof HeadsetProfile) {
                 HearingAidStatsLogUtils.addCurrentTimeToHistory(context,
-                        HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_CONNECTED);
+                        HearingAidStatsLogUtils.HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED);
             }
         }
     }
@@ -169,18 +167,13 @@
     /**
      * Returns the user category if the user is already categorized. Otherwise, checks the
      * history and sees if the user is categorized as one of {@link #CATEGORY_HEARING_AIDS},
-     * {@link #CATEGORY_NEW_HEARING_AIDS}, {@link #CATEGORY_HEARING_DEVICES}, and
-     * {@link #CATEGORY_NEW_HEARING_DEVICES}.
+     * {@link #CATEGORY_NEW_HEARING_AIDS}, {@link #CATEGORY_HEARABLE_DEVICES}, and
+     * {@link #CATEGORY_NEW_HEARABLE_DEVICES}.
      *
      * @param context the request context
      * @return the category which user belongs to
      */
     public static synchronized String getUserCategory(Context context) {
-        String userCategory = getSharedPreferences(context).getString(BT_HEARING_USER_CATEGORY, "");
-        if (!userCategory.isEmpty()) {
-            return userCategory;
-        }
-
         LinkedList<Long> hearingAidsConnectedHistory = getHistory(context,
                 HistoryType.TYPE_HEARING_AIDS_CONNECTED);
         if (hearingAidsConnectedHistory != null
@@ -192,29 +185,29 @@
             // will be categorized as CATEGORY_HEARING_AIDS.
             if (hearingAidsPairedHistory != null
                     && hearingAidsPairedHistory.size() >= VALID_PAIRED_EVENT_COUNT) {
-                userCategory = CATEGORY_NEW_HEARING_AIDS;
+                return CATEGORY_NEW_HEARING_AIDS;
             } else {
-                userCategory = CATEGORY_HEARING_AIDS;
+                return CATEGORY_HEARING_AIDS;
             }
         }
 
-        LinkedList<Long> hearingDevicesConnectedHistory = getHistory(context,
-                HistoryType.TYPE_HEARING_DEVICES_CONNECTED);
-        if (hearingDevicesConnectedHistory != null
-                && hearingDevicesConnectedHistory.size() >= VALID_CONNECTED_EVENT_COUNT) {
-            LinkedList<Long> hearingDevicesPairedHistory = getHistory(context,
-                    HistoryType.TYPE_HEARING_DEVICES_PAIRED);
+        LinkedList<Long> hearableDevicesConnectedHistory = getHistory(context,
+                HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED);
+        if (hearableDevicesConnectedHistory != null
+                && hearableDevicesConnectedHistory.size() >= VALID_CONNECTED_EVENT_COUNT) {
+            LinkedList<Long> hearableDevicesPairedHistory = getHistory(context,
+                    HistoryType.TYPE_HEARABLE_DEVICES_PAIRED);
             // Since paired history will be cleared after 30 days. If there's any record within 30
-            // days, the user will be categorized as CATEGORY_NEW_HEARING_DEVICES. Otherwise, the
-            // user will be categorized as CATEGORY_HEARING_DEVICES.
-            if (hearingDevicesPairedHistory != null
-                    && hearingDevicesPairedHistory.size() >= VALID_PAIRED_EVENT_COUNT) {
-                userCategory = CATEGORY_NEW_HEARING_DEVICES;
+            // days, the user will be categorized as CATEGORY_NEW_HEARABLE_DEVICES. Otherwise, the
+            // user will be categorized as CATEGORY_HEARABLE_DEVICES.
+            if (hearableDevicesPairedHistory != null
+                    && hearableDevicesPairedHistory.size() >= VALID_PAIRED_EVENT_COUNT) {
+                return CATEGORY_NEW_HEARABLE_DEVICES;
             } else {
-                userCategory = CATEGORY_HEARING_DEVICES;
+                return CATEGORY_HEARABLE_DEVICES;
             }
         }
-        return userCategory;
+        return "";
     }
 
     /**
@@ -245,7 +238,7 @@
     }
 
     /**
-     * Adds current timestamp into BT hearing devices related history.
+     * Adds current timestamp into BT hearing related devices history.
      * @param context the request context
      * @param type the type of history to store the data. See {@link HistoryType}.
      */
@@ -279,13 +272,13 @@
     static synchronized LinkedList<Long> getHistory(Context context, @HistoryType int type) {
         String spName = HISTORY_TYPE_TO_SP_NAME_MAPPING.get(type);
         if (BT_HEARING_AIDS_PAIRED_HISTORY.equals(spName)
-                || BT_HEARING_DEVICES_PAIRED_HISTORY.equals(spName)) {
+                || BT_HEARABLE_DEVICES_PAIRED_HISTORY.equals(spName)) {
             LinkedList<Long> history = convertToHistoryList(
                     getSharedPreferences(context).getString(spName, ""));
             removeRecordsBeforeDay(history, PAIRED_HISTORY_EXPIRED_DAY);
             return history;
         } else if (BT_HEARING_AIDS_CONNECTED_HISTORY.equals(spName)
-                || BT_HEARING_DEVICES_CONNECTED_HISTORY.equals(spName)) {
+                || BT_HEARABLE_DEVICES_CONNECTED_HISTORY.equals(spName)) {
             LinkedList<Long> history = convertToHistoryList(
                     getSharedPreferences(context).getString(spName, ""));
             removeRecordsBeforeDay(history, CONNECTED_HISTORY_EXPIRED_DAY);
@@ -352,9 +345,9 @@
         HISTORY_TYPE_TO_SP_NAME_MAPPING.put(
                 HistoryType.TYPE_HEARING_AIDS_CONNECTED, BT_HEARING_AIDS_CONNECTED_HISTORY);
         HISTORY_TYPE_TO_SP_NAME_MAPPING.put(
-                HistoryType.TYPE_HEARING_DEVICES_PAIRED, BT_HEARING_DEVICES_PAIRED_HISTORY);
+                HistoryType.TYPE_HEARABLE_DEVICES_PAIRED, BT_HEARABLE_DEVICES_PAIRED_HISTORY);
         HISTORY_TYPE_TO_SP_NAME_MAPPING.put(
-                HistoryType.TYPE_HEARING_DEVICES_CONNECTED, BT_HEARING_DEVICES_CONNECTED_HISTORY);
+                HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED, BT_HEARABLE_DEVICES_CONNECTED_HISTORY);
     }
     private HearingAidStatsLogUtils() {}
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
index 822a608..1040ac6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
@@ -36,6 +36,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import androidx.annotation.GuardedBy;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.telephony.SmsApplication;
@@ -56,13 +57,22 @@
 
     private static PowerAllowlistBackend sInstance;
 
+    private final Object mAllowlistedAppsLock = new Object();
+    private final Object mSysAllowlistedAppsLock = new Object();
+    private final Object mDefaultActiveAppsLock = new Object();
+
     private final Context mAppContext;
     private final IDeviceIdleController mDeviceIdleService;
+
+    @GuardedBy("mAllowlistedAppsLock")
     private final ArraySet<String> mAllowlistedApps = new ArraySet<>();
+    @GuardedBy("mSysAllowlistedAppsLock")
     private final ArraySet<String> mSysAllowlistedApps = new ArraySet<>();
+    @GuardedBy("mDefaultActiveAppsLock")
     private final ArraySet<String> mDefaultActiveApps = new ArraySet<>();
 
-    public PowerAllowlistBackend(Context context) {
+    @VisibleForTesting
+    PowerAllowlistBackend(Context context) {
         this(context, IDeviceIdleController.Stub.asInterface(
                 ServiceManager.getService(DEVICE_IDLE_SERVICE)));
     }
@@ -75,24 +85,25 @@
     }
 
     public int getAllowlistSize() {
-        return mAllowlistedApps.size();
-    }
-
-    /**
-    * Check if target package is in System allow list
-    */
-    public boolean isSysAllowlisted(String pkg) {
-        return mSysAllowlistedApps.contains(pkg);
-    }
-
-    /**
-     * Check if target package is in allow list
-     */
-    public boolean isAllowlisted(String pkg, int uid) {
-        if (mAllowlistedApps.contains(pkg)) {
-            return true;
+        synchronized (mAllowlistedAppsLock) {
+            return mAllowlistedApps.size();
         }
+    }
 
+    /** Check if target package is in System allow list */
+    public boolean isSysAllowlisted(String pkg) {
+        synchronized (mSysAllowlistedAppsLock) {
+            return mSysAllowlistedApps.contains(pkg);
+        }
+    }
+
+    /** Check if target package is in allow list */
+    public boolean isAllowlisted(String pkg, int uid) {
+        synchronized (mAllowlistedAppsLock) {
+            if (mAllowlistedApps.contains(pkg)) {
+                return true;
+            }
+        }
         if (isDefaultActiveApp(pkg, uid)) {
             return true;
         }
@@ -100,16 +111,16 @@
         return false;
     }
 
-    /**
-     * Check if it is default active app in multiple area(i.e. SMS, Dialer, Device admin..)
-     */
+    /** Check if it is default active app in multiple area */
     public boolean isDefaultActiveApp(String pkg, int uid) {
         // Additionally, check if pkg is default dialer/sms. They are considered essential apps and
         // should be automatically allowlisted (otherwise user may be able to set restriction on
         // them, leading to bad device behavior.)
 
-        if (mDefaultActiveApps.contains(pkg)) {
-            return true;
+        synchronized (mDefaultActiveAppsLock) {
+            if (mDefaultActiveApps.contains(pkg)) {
+                return true;
+            }
         }
 
         final DevicePolicyManager devicePolicyManager = mAppContext.getSystemService(
@@ -143,9 +154,7 @@
                 DEFAULT_SYSTEM_EXEMPT_POWER_RESTRICTIONS_ENABLED);
     }
 
-    /**
-     * Check if target package is in allow list except idle app
-     */
+    /** Check if target package is in allow list except idle app */
     public boolean isAllowlistedExceptIdle(String pkg) {
         try {
             return mDeviceIdleService.isPowerSaveWhitelistExceptIdleApp(pkg);
@@ -156,6 +165,7 @@
     }
 
     /**
+     * Check if target package is in allow list except idle app
      *
      * @param pkgs a list of packageName
      * @return true when one of package is in allow list
@@ -174,20 +184,21 @@
     }
 
     /**
-     * Add app into power save allow list.
+     * Add app into power save allow list
+     *
      * @param pkg packageName of the app
      */
-    // TODO: Fix all callers to pass in UID
     public void addApp(String pkg) {
         addApp(pkg, Process.INVALID_UID);
     }
 
     /**
-     * Add app into power save allow list.
+     * Add app into power save allow list
+     *
      * @param pkg packageName of the app
      * @param uid uid of the app
      */
-    public void addApp(String pkg, int uid) {
+    public synchronized void addApp(String pkg, int uid) {
         try {
             if (android.app.Flags.appRestrictionsApi()) {
                 if (uid == Process.INVALID_UID) {
@@ -204,7 +215,9 @@
             }
 
             mDeviceIdleService.addPowerSaveWhitelistApp(pkg);
-            mAllowlistedApps.add(pkg);
+            synchronized (mAllowlistedAppsLock) {
+                mAllowlistedApps.add(pkg);
+            }
         } catch (RemoteException e) {
             Log.w(TAG, "Unable to reach IDeviceIdleController", e);
         } catch (NameNotFoundException e) {
@@ -213,7 +226,8 @@
     }
 
     /**
-     * Remove package from power save allow list.
+     * Remove package from power save allow list
+     *
      * @param pkg packageName of the app
      */
     public void removeApp(String pkg) {
@@ -222,10 +236,11 @@
 
     /**
      * Remove package from power save allow list.
+     *
      * @param pkg packageName of the app
      * @param uid uid of the app
      */
-    public void removeApp(String pkg, int uid) {
+    public synchronized void removeApp(String pkg, int uid) {
         try {
             if (android.app.Flags.appRestrictionsApi()) {
                 if (uid == Process.INVALID_UID) {
@@ -241,7 +256,9 @@
             }
 
             mDeviceIdleService.removePowerSaveWhitelistApp(pkg);
-            mAllowlistedApps.remove(pkg);
+            synchronized (mAllowlistedAppsLock) {
+                mAllowlistedApps.remove(pkg);
+            }
         } catch (RemoteException e) {
             Log.w(TAG, "Unable to reach IDeviceIdleController", e);
         } catch (NameNotFoundException e) {
@@ -249,25 +266,33 @@
         }
     }
 
-    /**
-     * Refresh all of lists
-     */
+    /** Refresh all of lists */
     @VisibleForTesting
-    public void refreshList() {
-        mSysAllowlistedApps.clear();
-        mAllowlistedApps.clear();
-        mDefaultActiveApps.clear();
+    public synchronized void refreshList() {
+        synchronized (mSysAllowlistedAppsLock) {
+            mSysAllowlistedApps.clear();
+        }
+        synchronized (mAllowlistedAppsLock) {
+            mAllowlistedApps.clear();
+        }
+        synchronized (mDefaultActiveAppsLock) {
+            mDefaultActiveApps.clear();
+        }
         if (mDeviceIdleService == null) {
             return;
         }
         try {
             final String[] allowlistedApps = mDeviceIdleService.getFullPowerWhitelist();
-            for (String app : allowlistedApps) {
-                mAllowlistedApps.add(app);
+            synchronized (mAllowlistedAppsLock) {
+                for (String app : allowlistedApps) {
+                    mAllowlistedApps.add(app);
+                }
             }
             final String[] sysAllowlistedApps = mDeviceIdleService.getSystemPowerWhitelist();
-            for (String app : sysAllowlistedApps) {
-                mSysAllowlistedApps.add(app);
+            synchronized (mSysAllowlistedAppsLock) {
+                for (String app : sysAllowlistedApps) {
+                    mSysAllowlistedApps.add(app);
+                }
             }
             final boolean hasTelephony = mAppContext.getPackageManager().hasSystemFeature(
                     PackageManager.FEATURE_TELEPHONY);
@@ -278,26 +303,28 @@
 
             if (hasTelephony) {
                 if (defaultSms != null) {
-                    mDefaultActiveApps.add(defaultSms.getPackageName());
+                    synchronized (mDefaultActiveAppsLock) {
+                        mDefaultActiveApps.add(defaultSms.getPackageName());
+                    }
                 }
                 if (!TextUtils.isEmpty(defaultDialer)) {
-                    mDefaultActiveApps.add(defaultDialer);
+                    synchronized (mDefaultActiveAppsLock) {
+                        mDefaultActiveApps.add(defaultDialer);
+                    }
                 }
             }
-        } catch (RemoteException e) {
-            Log.w(TAG, "Unable to reach IDeviceIdleController", e);
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to invoke refreshList()", e);
         }
     }
 
-    /**
-     * @param context
-     * @return a PowerAllowlistBackend object
-     */
+    /** Get the {@link PowerAllowlistBackend} instance */
     public static PowerAllowlistBackend getInstance(Context context) {
-        if (sInstance == null) {
-            sInstance = new PowerAllowlistBackend(context);
+        synchronized (PowerAllowlistBackend.class) {
+            if (sInstance == null) {
+                sInstance = new PowerAllowlistBackend(context);
+            }
+            return sInstance;
         }
-        return sInstance;
     }
-
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
new file mode 100644
index 0000000..d69c87b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.satellite
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.OutcomeReceiver
+import android.telephony.satellite.SatelliteManager
+import android.util.Log
+import android.view.WindowManager
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import com.android.settingslib.wifi.WifiUtils
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Dispatchers.Default
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.TimeoutException
+import kotlin.coroutines.resume
+
+/** A util for Satellite dialog */
+object SatelliteDialogUtils {
+
+    /**
+     * Uses to start Satellite dialog to prevent users from using the BT, Airplane Mode, and
+     * Wifi during the satellite mode is on.
+     */
+    @JvmStatic
+    fun mayStartSatelliteWarningDialog(
+            context: Context,
+            lifecycleOwner: LifecycleOwner,
+            type: Int,
+            allowClick: (isAllowed: Boolean) -> Unit
+    ): Job {
+        return mayStartSatelliteWarningDialog(
+                context, lifecycleOwner.lifecycleScope, type, allowClick)
+    }
+
+    /**
+     * Uses to start Satellite dialog to prevent users from using the BT, Airplane Mode, and
+     * Wifi during the satellite mode is on.
+     */
+    @JvmStatic
+    fun mayStartSatelliteWarningDialog(
+            context: Context,
+            coroutineScope: CoroutineScope,
+            type: Int,
+            allowClick: (isAllowed: Boolean) -> Unit
+    ): Job =
+            coroutineScope.launch {
+                var isSatelliteModeOn = false
+                try {
+                    isSatelliteModeOn = requestIsEnabled(context)
+                } catch (e: InterruptedException) {
+                    Log.w(TAG, "Error to get satellite status : $e")
+                } catch (e: ExecutionException) {
+                    Log.w(TAG, "Error to get satellite status : $e")
+                } catch (e: TimeoutException) {
+                    Log.w(TAG, "Error to get satellite status : $e")
+                }
+
+                if (isSatelliteModeOn) {
+                    startSatelliteWarningDialog(context, type)
+                }
+                withContext(Dispatchers.Main) {
+                    allowClick(!isSatelliteModeOn)
+                }
+            }
+
+    private fun startSatelliteWarningDialog(context: Context, type: Int) {
+        context.startActivity(Intent(Intent.ACTION_MAIN).apply {
+            component = ComponentName(
+                    "com.android.settings",
+                    "com.android.settings.network.SatelliteWarningDialogActivity"
+            )
+            putExtra(WifiUtils.DIALOG_WINDOW_TYPE,
+                    WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG)
+            putExtra(EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG, type)
+            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
+        })
+    }
+
+    /**
+     * Checks if the satellite modem is enabled.
+     *
+     * @param executor The executor to run the asynchronous operation on
+     * @return A ListenableFuture that will resolve to `true` if the satellite modem enabled,
+     *         `false` otherwise.
+     */
+    private suspend fun requestIsEnabled(
+            context: Context,
+    ): Boolean = withContext(Default) {
+        val satelliteManager: SatelliteManager? =
+                context.getSystemService(SatelliteManager::class.java)
+        if (satelliteManager == null) {
+            Log.w(TAG, "SatelliteManager is null")
+            return@withContext false
+        }
+
+        suspendCancellableCoroutine {continuation ->
+            satelliteManager?.requestIsEnabled(Default.asExecutor(),
+                    object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> {
+                        override fun onResult(result: Boolean) {
+                            Log.i(TAG, "Satellite modem enabled status: $result")
+                            continuation.resume(result)
+                        }
+
+                        override fun onError(error: SatelliteManager.SatelliteException) {
+                            super.onError(error)
+                            Log.w(TAG, "Can't get satellite modem enabled status", error)
+                            continuation.resume(false)
+                        }
+                    })
+        }
+    }
+
+    const val TAG = "SatelliteDialogUtils"
+
+    const val EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG: String =
+            "extra_type_of_satellite_warning_dialog"
+    const val TYPE_IS_UNKNOWN = -1
+    const val TYPE_IS_WIFI = 0
+    const val TYPE_IS_BLUETOOTH = 1
+    const val TYPE_IS_AIRPLANE_MODE = 2
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt
index 2a44511..a939ed1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt
@@ -17,6 +17,7 @@
 package com.android.settingslib.statusbar.notification.data.repository
 
 import android.app.NotificationManager
+import android.provider.Settings
 import com.android.settingslib.statusbar.notification.data.model.ZenMode
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -28,10 +29,14 @@
     override val notificationPolicy: StateFlow<NotificationManager.Policy?>
         get() = mutableNotificationPolicy.asStateFlow()
 
-    private val mutableZenMode = MutableStateFlow<ZenMode?>(null)
+    private val mutableZenMode = MutableStateFlow<ZenMode?>(ZenMode(Settings.Global.ZEN_MODE_OFF))
     override val zenMode: StateFlow<ZenMode?>
         get() = mutableZenMode.asStateFlow()
 
+    init {
+        updateNotificationPolicy()
+    }
+
     fun updateNotificationPolicy(policy: NotificationManager.Policy?) {
         mutableNotificationPolicy.value = policy
     }
@@ -48,13 +53,14 @@
     suppressedVisualEffects: Int = NotificationManager.Policy.SUPPRESSED_EFFECTS_UNSET,
     state: Int = NotificationManager.Policy.STATE_UNSET,
     priorityConversationSenders: Int = NotificationManager.Policy.CONVERSATION_SENDERS_NONE,
-) = updateNotificationPolicy(
-    NotificationManager.Policy(
-        priorityCategories,
-        priorityCallSenders,
-        priorityMessageSenders,
-        suppressedVisualEffects,
-        state,
-        priorityConversationSenders,
+) =
+    updateNotificationPolicy(
+        NotificationManager.Policy(
+            priorityCategories,
+            priorityCallSenders,
+            priorityMessageSenders,
+            suppressedVisualEffects,
+            state,
+            priorityConversationSenders,
+        )
     )
-)
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 65a5317..36e396fb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -72,7 +72,11 @@
 
     suspend fun setVolume(audioStream: AudioStream, volume: Int)
 
-    suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean)
+    /**
+     * Mutes and un-mutes [audioStream]. Returns true when the state changes and false the
+     * otherwise.
+     */
+    suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean
 
     suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode)
 
@@ -164,14 +168,20 @@
             audioManager.setStreamVolume(audioStream.value, volume, 0)
         }
 
-    override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
-        withContext(backgroundCoroutineContext) {
-            audioManager.adjustStreamVolume(
-                audioStream.value,
-                if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE,
-                0,
-            )
+    override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean {
+        return withContext(backgroundCoroutineContext) {
+            if (isMuted == audioManager.isStreamMute(audioStream.value)) {
+                false
+            } else {
+                audioManager.adjustStreamVolume(
+                    audioStream.value,
+                    if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE,
+                    0,
+                )
+                true
+            }
         }
+    }
 
     override suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) {
         withContext(backgroundCoroutineContext) { audioManager.ringerMode = mode.value }
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerExt.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerExt.kt
deleted file mode 100644
index 1f037c0..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerExt.kt
+++ /dev/null
@@ -1,100 +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.
- */
-
-package com.android.settingslib.volume.data.repository
-
-import android.media.MediaMetadata
-import android.media.session.MediaController
-import android.media.session.MediaSession
-import android.media.session.PlaybackState
-import android.os.Bundle
-import android.os.Handler
-import kotlinx.coroutines.channels.ProducerScope
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.launch
-
-/** [MediaController.Callback] flow representation. */
-fun MediaController.stateChanges(handler: Handler): Flow<MediaControllerChange> {
-    return callbackFlow {
-        val callback = MediaControllerCallbackProducer(this)
-        registerCallback(callback, handler)
-        awaitClose { unregisterCallback(callback) }
-    }
-}
-
-/** Models particular change event received by [MediaController.Callback]. */
-sealed interface MediaControllerChange {
-
-    data object SessionDestroyed : MediaControllerChange
-
-    data class SessionEvent(val event: String, val extras: Bundle?) : MediaControllerChange
-
-    data class PlaybackStateChanged(val state: PlaybackState?) : MediaControllerChange
-
-    data class MetadataChanged(val metadata: MediaMetadata?) : MediaControllerChange
-
-    data class QueueChanged(val queue: MutableList<MediaSession.QueueItem>?) :
-        MediaControllerChange
-
-    data class QueueTitleChanged(val title: CharSequence?) : MediaControllerChange
-
-    data class ExtrasChanged(val extras: Bundle?) : MediaControllerChange
-
-    data class AudioInfoChanged(val info: MediaController.PlaybackInfo?) : MediaControllerChange
-}
-
-private class MediaControllerCallbackProducer(
-    private val producingScope: ProducerScope<MediaControllerChange>
-) : MediaController.Callback() {
-
-    override fun onSessionDestroyed() {
-        send(MediaControllerChange.SessionDestroyed)
-    }
-
-    override fun onSessionEvent(event: String, extras: Bundle?) {
-        send(MediaControllerChange.SessionEvent(event, extras))
-    }
-
-    override fun onPlaybackStateChanged(state: PlaybackState?) {
-        send(MediaControllerChange.PlaybackStateChanged(state))
-    }
-
-    override fun onMetadataChanged(metadata: MediaMetadata?) {
-        send(MediaControllerChange.MetadataChanged(metadata))
-    }
-
-    override fun onQueueChanged(queue: MutableList<MediaSession.QueueItem>?) {
-        send(MediaControllerChange.QueueChanged(queue))
-    }
-
-    override fun onQueueTitleChanged(title: CharSequence?) {
-        send(MediaControllerChange.QueueTitleChanged(title))
-    }
-
-    override fun onExtrasChanged(extras: Bundle?) {
-        send(MediaControllerChange.ExtrasChanged(extras))
-    }
-
-    override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) {
-        send(MediaControllerChange.AudioInfoChanged(info))
-    }
-
-    private fun send(change: MediaControllerChange) {
-        producingScope.launch { producingScope.send(change) }
-    }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
index 33f917e..0e5ebda 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
@@ -25,6 +25,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.map
 
 /** Provides audio stream state and an ability to change it */
@@ -46,8 +47,16 @@
     val ringerMode: StateFlow<RingerMode>
         get() = audioRepository.ringerMode
 
-    suspend fun setVolume(audioStream: AudioStream, volume: Int) =
+    suspend fun setVolume(audioStream: AudioStream, volume: Int) {
+        val streamModel = getAudioStream(audioStream).first()
+        val oldVolume = streamModel.volume
         audioRepository.setVolume(audioStream, volume)
+        when {
+            volume == streamModel.minVolume -> setMuted(audioStream, true)
+            oldVolume == streamModel.minVolume && volume > streamModel.minVolume ->
+                setMuted(audioStream, false)
+        }
+    }
 
     suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) {
         if (audioStream.value == AudioManager.STREAM_RING) {
@@ -55,7 +64,16 @@
                 if (isMuted) AudioManager.RINGER_MODE_VIBRATE else AudioManager.RINGER_MODE_NORMAL
             audioRepository.setRingerMode(audioStream, RingerMode(mode))
         }
-        audioRepository.setMuted(audioStream, isMuted)
+        val mutedChanged = audioRepository.setMuted(audioStream, isMuted)
+        if (mutedChanged && !isMuted) {
+            with(getAudioStream(audioStream).first()) {
+                if (volume == minVolume) {
+                    // Slightly increase volume when user un-mutes the stream that is lowered
+                    // down to its minimum
+                    setVolume(audioStream, volume + 1)
+                }
+            }
+        }
     }
 
     /** Checks if the volume can be changed via the UI. */
diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp
index f4ddd0a..e125083 100644
--- a/packages/SettingsLib/tests/robotests/Android.bp
+++ b/packages/SettingsLib/tests/robotests/Android.bp
@@ -41,7 +41,10 @@
 //###########################################################
 android_robolectric_test {
     name: "SettingsLibRoboTests",
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
     static_libs: [
         "Settings_robolectric_meta_service_file",
         "Robolectric_shadows_androidx_fragment_upstream",
@@ -51,6 +54,7 @@
         "androidx.core_core",
         "flag-junit",
         "settingslib_media_flags_lib",
+        "settingslib_illustrationpreference_flags_lib",
         "testng", // TODO: remove once JUnit on Android provides assertThrows
     ],
     java_resource_dirs: ["config"],
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 f197f9e..7a2818d 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
@@ -28,7 +28,7 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
 import android.content.Context;
-import android.content.pm.PackageInfo;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
@@ -80,7 +80,8 @@
     private static final String CONTROL_METADATA =
             "<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" + STRING_METADATA
                     + "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>";
-    private static final String FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name";
+    private static final String TEST_EXCLUSIVE_MANAGER_PACKAGE = "com.test.manager";
+    private static final String TEST_EXCLUSIVE_MANAGER_COMPONENT = "com.test.manager/.component";
 
     @Before
     public void setUp() {
@@ -399,7 +400,7 @@
     }
 
     @Test
-    public void isExclusivelyManagedBluetoothDevice_isNotExclusivelyManaged_returnFalse() {
+    public void isExclusivelyManaged_hasNoManager_returnFalse() {
         when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
                 null);
 
@@ -408,45 +409,85 @@
     }
 
     @Test
-    public void isExclusivelyManagedBluetoothDevice_isNotInAllowList_returnFalse() {
+    public void isExclusivelyManaged_hasPackageName_packageNotInstalled_returnFalse()
+            throws Exception {
         when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
-                FAKE_EXCLUSIVE_MANAGER_NAME.getBytes());
+                TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager)
+                .getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
 
         assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
                 mBluetoothDevice)).isEqualTo(false);
     }
 
     @Test
-    public void isExclusivelyManagedBluetoothDevice_packageNotInstalled_returnFalse()
+    public void isExclusivelyManaged_hasComponentName_packageNotInstalled_returnFalse()
             throws Exception {
-        final String exclusiveManagerName =
-                BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
-                        FAKE_EXCLUSIVE_MANAGER_NAME);
-
         when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
-                exclusiveManagerName.getBytes());
+                TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
-        doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager).getPackageInfo(
-                exclusiveManagerName, 0);
+        doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager)
+                .getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
 
         assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
-                mBluetoothDevice)).isEqualTo(false);
+            mBluetoothDevice)).isEqualTo(false);
     }
 
     @Test
-    public void isExclusivelyManagedBluetoothDevice_isExclusivelyManaged_returnTrue()
-            throws Exception {
-        final String exclusiveManagerName =
-                BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
-                        FAKE_EXCLUSIVE_MANAGER_NAME);
-
-        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
-                exclusiveManagerName.getBytes());
+    public void isExclusivelyManaged_hasPackageName_packageNotEnabled_returnFalse()
+             throws Exception {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.enabled = false;
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
-        doReturn(new PackageInfo()).when(mPackageManager).getPackageInfo(exclusiveManagerName, 0);
+        doReturn(appInfo).when(mPackageManager).getApplicationInfo(
+                TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+                TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
 
         assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
-                mBluetoothDevice)).isEqualTo(true);
+            mBluetoothDevice)).isEqualTo(false);
+    }
+
+    @Test
+    public void isExclusivelyManaged_hasComponentName_packageNotEnabled_returnFalse()
+            throws Exception {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.enabled = false;
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        doReturn(appInfo).when(mPackageManager).getApplicationInfo(
+                TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+                TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
+
+        assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+            mBluetoothDevice)).isEqualTo(false);
+    }
+
+    @Test
+    public void isExclusivelyManaged_hasPackageName_packageInstalledAndEnabled_returnTrue()
+            throws Exception {
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+                TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo(
+                TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+
+        assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+            mBluetoothDevice)).isEqualTo(true);
+    }
+
+    @Test
+    public void isExclusivelyManaged_hasComponentName_packageInstalledAndEnabled_returnTrue()
+            throws Exception {
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+                TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo(
+                TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+
+        assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+            mBluetoothDevice)).isEqualTo(true);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index b356f54..0d81494 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -753,33 +753,70 @@
     }
 
     @Test
-    public void getConnectionSummary_testHearingAidBatteryWithoutInCall_returnActiveBattery() {
+    public void getConnectionSummary_testHearingAidLeftEarBatteryNotInCall_returnActiveBattery() {
         // Arrange:
-        //   1. Profile:       {HEARING_AID, Connected, Active}
+        //   1. Profile:       {HEARING_AID, Connected, Active, Left ear}
         //   2. Battery Level: 10
         //   3. Audio Manager: Normal (Without In Call)
         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+        mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
         mBatteryLevel = 10;
 
         // Act & Assert:
-        //   Get "Active, 10% battery" result with Battery Level 10.
-        assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, 10% battery");
+        //   Get "Active. L: 10% battery." result with Battery Level 10.
+        assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active. L: 10% battery.");
     }
 
     @Test
-    public void getTvConnectionSummary_testHearingAidBatteryWithoutInCall_returnBattery() {
+    public void getTvConnectionSummary_testHearingAidLeftEarBatteryWithoutInCall_returnBattery() {
         // Arrange:
-        //   1. Profile:       {HEARING_AID, Connected, Active}
+        //   1. Profile:       {HEARING_AID, Connected, Active, Left ear}
         //   2. Battery Level: 10
         //   3. Audio Manager: Normal (Without In Call)
         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+        mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
         mBatteryLevel = 10;
 
         // Act & Assert:
-        //   Get "Active, 10% battery" result with Battery Level 10.
-        assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo("Battery 10%");
+        //   Get "Left 10%" result with Battery Level 10.
+        assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo("Left 10%");
+    }
+
+    @Test
+    public void getConnectionSummary_testHearingAidLeftEarBatteryInCall_returnActiveBattery() {
+        // Arrange:
+        //   1. Profile:       {HEARING_AID, Connected, Active, Left ear}
+        //   2. Battery Level: 10
+        //   3. Audio Manager: In Call
+        updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+        mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
+        mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+        mBatteryLevel = 10;
+
+        // Act & Assert:
+        //   Get "Active. L: 10% battery." result with Battery Level 10.
+        assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active. L: 10% battery.");
+    }
+
+    @Test
+    public void getTvConnectionSummary_testHearingAidLeftEarBatteryInCall_returnBattery() {
+        // Arrange:
+        //   1. Profile:       {HEARING_AID, Connected, Active, Left ear}
+        //   2. Battery Level: 10
+        //   3. Audio Manager: In Call
+        updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+        mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
+        mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+        mBatteryLevel = 10;
+
+        // Act & Assert:
+        //   Get "Left 10%" result with Battery Level 10.
+        assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo(
+                "Left 10%");
     }
 
     @Test
@@ -851,35 +888,45 @@
     }
 
     @Test
-    public void getConnectionSummary_testHearingAidBatteryInCall_returnActiveBattery() {
+    public void getConnectionSummary_testHearingAidBothEarBattery_returnActiveBattery() {
         // Arrange:
-        //   1. Profile:       {HEARING_AID, Connected, Active}
+        //   1. Profile:       {HEARING_AID, Connected, Active, Both ear}
         //   2. Battery Level: 10
         //   3. Audio Manager: In Call
+        mCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo());
         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+        mSubCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
+        updateSubDeviceProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+        mCachedDevice.setSubDevice(mSubCachedDevice);
         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
         mAudioManager.setMode(AudioManager.MODE_IN_CALL);
         mBatteryLevel = 10;
 
         // Act & Assert:
-        //   Get "Active, 10% battery" result with Battery Level 10.
-        assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, 10% battery");
+        //   Get "Active. L: 10%, R: 10% battery." result with Battery Level 10.
+        assertThat(mCachedDevice.getConnectionSummary().toString())
+                .isEqualTo("Active. L: 10%, R: 10% battery.");
     }
 
     @Test
-    public void getTvConnectionSummary_testHearingAidBatteryInCall_returnBattery() {
+    public void getTvConnectionSummary_testHearingAidBothEarBattery_returnActiveBattery() {
         // Arrange:
-        //   1. Profile:       {HEARING_AID, Connected, Active}
+        //   1. Profile:       {HEARING_AID, Connected, Active, Both ear}
         //   2. Battery Level: 10
         //   3. Audio Manager: In Call
+        mCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo());
         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+        mSubCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
+        updateSubDeviceProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+        mCachedDevice.setSubDevice(mSubCachedDevice);
         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
         mAudioManager.setMode(AudioManager.MODE_IN_CALL);
         mBatteryLevel = 10;
 
         // Act & Assert:
-        //   Get "Active, 10% battery" result with Battery Level 10.
-        assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo("Battery 10%");
+        //   Get "Left 10% Right 10%" result with Battery Level 10.
+        assertThat(mCachedDevice.getTvConnectionSummary().toString())
+                .isEqualTo("Left 10% Right 10%");
     }
 
     @Test
@@ -1151,7 +1198,7 @@
         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
         when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
         when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
                 "true".getBytes());
         when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
@@ -1160,7 +1207,7 @@
                 TWS_BATTERY_RIGHT.getBytes());
 
         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
-                "Active, L: 15% battery, R: 25% battery");
+                "Active. L: 15%, R: 25% battery.");
     }
 
     @Test
@@ -1169,7 +1216,7 @@
         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
         when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
         when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
                 "true".getBytes());
         when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
@@ -1214,11 +1261,7 @@
                 TWS_BATTERY_RIGHT.getBytes());
 
         assertThat(mCachedDevice.getTvConnectionSummary().toString())
-                .isEqualTo(
-                        mContext.getString(R.string.bluetooth_battery_level_untethered_left, "15%")
-                                + " "
-                                + mContext.getString(
-                                        R.string.bluetooth_battery_level_untethered_right, "25%"));
+                .isEqualTo("Left 15% Right 25%");
     }
 
     @Test
@@ -1235,10 +1278,8 @@
                 .thenReturn(TWS_BATTERY_RIGHT.getBytes());
 
         int lowBatteryColor = mContext.getColor(LOW_BATTERY_COLOR);
-        String leftBattery =
-                mContext.getString(R.string.bluetooth_battery_level_untethered_left, "15%");
-        String rightBattery =
-                mContext.getString(R.string.bluetooth_battery_level_untethered_right, "25%");
+        String leftBattery = "Left 15%";
+        String rightBattery = "Right 25%";
 
         // Default low battery threshold, only left battery is low
         CharSequence summary = mCachedDevice.getTvConnectionSummary(LOW_BATTERY_COLOR);
@@ -1703,6 +1744,30 @@
     }
 
     @Test
+    public void setName_memberDeviceNameIsSet() {
+        when(mDevice.getAlias()).thenReturn(DEVICE_NAME);
+        when(mSubDevice.getAlias()).thenReturn(DEVICE_NAME);
+
+        mCachedDevice.addMemberDevice(mSubCachedDevice);
+        mCachedDevice.setName(DEVICE_ALIAS);
+
+        verify(mDevice).setAlias(DEVICE_ALIAS);
+        verify(mSubDevice).setAlias(DEVICE_ALIAS);
+    }
+
+    @Test
+    public void setName_subDeviceNameIsSet() {
+        when(mDevice.getAlias()).thenReturn(DEVICE_NAME);
+        when(mSubDevice.getAlias()).thenReturn(DEVICE_NAME);
+
+        mCachedDevice.setSubDevice(mSubCachedDevice);
+        mCachedDevice.setName(DEVICE_ALIAS);
+
+        verify(mDevice).setAlias(DEVICE_ALIAS);
+        verify(mSubDevice).setAlias(DEVICE_ALIAS);
+    }
+
+    @Test
     public void getProfileConnectionState_nullProfile_returnDisconnected() {
         assertThat(mCachedDevice.getProfileConnectionState(null)).isEqualTo(
                 BluetoothProfile.STATE_DISCONNECTED);
@@ -1717,16 +1782,6 @@
                 BluetoothProfile.STATE_CONNECTED);
     }
 
-    private void updateProfileStatus(LocalBluetoothProfile profile, int status) {
-        doReturn(status).when(profile).getConnectionStatus(mDevice);
-        mCachedDevice.onProfileStateChanged(profile, status);
-    }
-
-    private void updateSubDeviceProfileStatus(LocalBluetoothProfile profile, int status) {
-        doReturn(status).when(profile).getConnectionStatus(mSubDevice);
-        mSubCachedDevice.onProfileStateChanged(profile, status);
-    }
-
     @Test
     public void getSubDevice_setSubDevice() {
         mCachedDevice.setSubDevice(mSubCachedDevice);
@@ -2006,6 +2061,29 @@
         assertThat(mCachedDevice.getConnectionSummary(false)).isNull();
     }
 
+    private void updateProfileStatus(LocalBluetoothProfile profile, int status) {
+        doReturn(status).when(profile).getConnectionStatus(mDevice);
+        mCachedDevice.onProfileStateChanged(profile, status);
+        updateConnectionStatus(mCachedDevice);
+    }
+
+    private void updateSubDeviceProfileStatus(LocalBluetoothProfile profile, int status) {
+        doReturn(status).when(profile).getConnectionStatus(mSubDevice);
+        mSubCachedDevice.onProfileStateChanged(profile, status);
+        updateConnectionStatus(mSubCachedDevice);
+    }
+
+    private void updateConnectionStatus(CachedBluetoothDevice cachedBluetoothDevice) {
+        for (LocalBluetoothProfile profile : cachedBluetoothDevice.getProfiles()) {
+            int status = cachedBluetoothDevice.getProfileConnectionState(profile);
+            if (status == BluetoothProfile.STATE_CONNECTED) {
+                when(cachedBluetoothDevice.getDevice().isConnected()).thenReturn(true);
+                return;
+            }
+        }
+        when(cachedBluetoothDevice.getDevice().isConnected()).thenReturn(false);
+    }
+
     private HearingAidInfo getLeftAshaHearingAidInfo() {
         return new HearingAidInfo.Builder()
                 .setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index 1f0da90..4188d2e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -299,6 +299,7 @@
         mHearingAidDeviceManager.setSubDeviceIfNeeded(mCachedDevice2);
 
         assertThat(mCachedDevice1.getSubDevice()).isEqualTo(mCachedDevice2);
+        verify(mDevice2).setAlias(DEVICE_ALIAS_1);
     }
 
     /**
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java
index bd5a022..cd16721 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java
@@ -145,20 +145,29 @@
     }
 
     @Test
-    public void getUserCategory_hearingDevicesUser() {
-        prepareHearingDevicesUserHistory();
+    public void getUserCategory_hearableDevicesUser() {
+        prepareHearableDevicesUserHistory();
 
         assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo(
-                HearingAidStatsLogUtils.CATEGORY_HEARING_DEVICES);
+                HearingAidStatsLogUtils.CATEGORY_HEARABLE_DEVICES);
     }
 
     @Test
-    public void getUserCategory_newHearingDevicesUser() {
-        prepareHearingDevicesUserHistory();
+    public void getUserCategory_newHearableDevicesUser() {
+        prepareHearableDevicesUserHistory();
         prepareNewUserHistory();
 
         assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo(
-                HearingAidStatsLogUtils.CATEGORY_NEW_HEARING_DEVICES);
+                HearingAidStatsLogUtils.CATEGORY_NEW_HEARABLE_DEVICES);
+    }
+
+    @Test
+    public void getUserCategory_bothHearingAidsAndHearableDevicesUser_returnHearingAidsUser() {
+        prepareHearingAidsUserHistory();
+        prepareHearableDevicesUserHistory();
+
+        assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo(
+                HearingAidStatsLogUtils.CATEGORY_HEARING_AIDS);
     }
 
     private long convertToStartOfDayTime(long timestamp) {
@@ -176,12 +185,12 @@
         }
     }
 
-    private void prepareHearingDevicesUserHistory() {
+    private void prepareHearableDevicesUserHistory() {
         final long todayStartOfDay = convertToStartOfDayTime(System.currentTimeMillis());
         for (int i = CONNECTED_HISTORY_EXPIRED_DAY - 1; i >= 0; i--) {
             final long data = todayStartOfDay - TimeUnit.DAYS.toMillis(i);
             HearingAidStatsLogUtils.addToHistory(mContext,
-                    HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_CONNECTED, data);
+                    HearingAidStatsLogUtils.HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED, data);
         }
     }
 
@@ -191,6 +200,6 @@
         HearingAidStatsLogUtils.addToHistory(mContext,
                 HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_PAIRED, data);
         HearingAidStatsLogUtils.addToHistory(mContext,
-                HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_PAIRED, data);
+                HearingAidStatsLogUtils.HistoryType.TYPE_HEARABLE_DEVICES_PAIRED, data);
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
new file mode 100644
index 0000000..aeda1ed6
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.satellite
+
+import android.content.Context
+import android.content.Intent
+import android.os.OutcomeReceiver
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.telephony.satellite.SatelliteManager
+import android.telephony.satellite.SatelliteManager.SatelliteException
+import android.util.AndroidRuntimeException
+import androidx.test.core.app.ApplicationProvider
+import com.android.internal.telephony.flags.Flags
+import com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_WIFI
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+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.any
+import org.mockito.Mockito.`when`
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.verify
+import org.mockito.internal.verification.Times
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class SatelliteDialogUtilsTest {
+    @JvmField
+    @Rule
+    val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+    @Spy
+    var context: Context = ApplicationProvider.getApplicationContext()
+    @Mock
+    private lateinit var satelliteManager: SatelliteManager
+
+    private val coroutineScope = CoroutineScope(Dispatchers.Main)
+
+    @Before
+    fun setUp() {
+        `when`(context.getSystemService(SatelliteManager::class.java))
+                .thenReturn(satelliteManager)
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun mayStartSatelliteWarningDialog_satelliteIsOn_showWarningDialog() = runBlocking {
+        `when`(
+                satelliteManager.requestIsEnabled(
+                        any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
+                )
+        )
+                .thenAnswer { invocation ->
+                    val receiver = invocation
+                            .getArgument<
+                                    OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
+                                    1
+                            )
+                    receiver.onResult(true)
+                    null
+                }
+
+        try {
+            SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+                    context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+                        assertTrue(it)
+                })
+        } catch (e: AndroidRuntimeException) {
+            // Catch exception of starting activity .
+        }
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun mayStartSatelliteWarningDialog_satelliteIsOff_notShowWarningDialog() = runBlocking {
+        `when`(
+                satelliteManager.requestIsEnabled(
+                        any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
+                )
+        )
+                .thenAnswer { invocation ->
+                    val receiver = invocation
+                            .getArgument<
+                                    OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
+                                    1
+                            )
+                    receiver.onResult(false)
+                    null
+                }
+
+
+        SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+            context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+                assertFalse(it)
+            })
+
+        verify(context, Times(0)).startActivity(any<Intent>())
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun mayStartSatelliteWarningDialog_noSatelliteManager_notShowWarningDialog() = runBlocking {
+        `when`(context.getSystemService(SatelliteManager::class.java))
+                .thenReturn(null)
+
+        SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+            context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+                assertFalse(it)
+            })
+
+        verify(context, Times(0)).startActivity(any<Intent>())
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun mayStartSatelliteWarningDialog_satelliteErrorResult_notShowWarningDialog() = runBlocking {
+        `when`(
+                satelliteManager.requestIsEnabled(
+                        any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
+                )
+        )
+                .thenAnswer { invocation ->
+                    val receiver = invocation
+                            .getArgument<
+                                    OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
+                                    1
+                            )
+                    receiver.onError(SatelliteException(SatelliteManager.SATELLITE_RESULT_ERROR))
+                    null
+                }
+
+
+        SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+            context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+                assertFalse(it)
+            })
+
+        verify(context, Times(0)).startActivity(any<Intent>())
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
index 6590bbd..ca53fc2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
@@ -26,10 +26,14 @@
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.drawable.AnimatedImageDrawable;
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.AnimationDrawable;
 import android.net.Uri;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
@@ -39,11 +43,13 @@
 import androidx.preference.PreferenceViewHolder;
 import androidx.test.core.app.ApplicationProvider;
 
+import com.android.settingslib.widget.flags.Flags;
 import com.android.settingslib.widget.preference.illustration.R;
 
 import com.airbnb.lottie.LottieAnimationView;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -51,10 +57,14 @@
 import org.robolectric.Robolectric;
 import org.robolectric.RobolectricTestRunner;
 
+import java.io.ByteArrayInputStream;
+
 
 @RunWith(RobolectricTestRunner.class)
 public class IllustrationPreferenceTest {
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock
     private ViewGroup mRootView;
     private Uri mImageUri;
@@ -66,6 +76,7 @@
     private final Context mContext = ApplicationProvider.getApplicationContext();
     private IllustrationPreference.OnBindListener mOnBindListener;
     private LottieAnimationView mOnBindListenerAnimationView;
+    private FrameLayout mIllustrationFrame;
 
     @Before
     public void setUp() {
@@ -75,14 +86,14 @@
         mBackgroundView = new ImageView(mContext);
         mAnimationView = spy(new LottieAnimationView(mContext));
         mMiddleGroundLayout = new FrameLayout(mContext);
-        final FrameLayout illustrationFrame = new FrameLayout(mContext);
-        illustrationFrame.setLayoutParams(
+        mIllustrationFrame = new FrameLayout(mContext);
+        mIllustrationFrame.setLayoutParams(
                 new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                         ViewGroup.LayoutParams.WRAP_CONTENT));
         doReturn(mMiddleGroundLayout).when(mRootView).findViewById(R.id.middleground_layout);
         doReturn(mBackgroundView).when(mRootView).findViewById(R.id.background_view);
         doReturn(mAnimationView).when(mRootView).findViewById(R.id.lottie_view);
-        doReturn(illustrationFrame).when(mRootView).findViewById(R.id.illustration_frame);
+        doReturn(mIllustrationFrame).when(mRootView).findViewById(R.id.illustration_frame);
         mViewHolder = spy(PreferenceViewHolder.createInstanceForTests(mRootView));
 
         final AttributeSet attributeSet = Robolectric.buildAttributeSet().build();
@@ -158,11 +169,13 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_AUTO_HIDE_EMPTY_LOTTIE_RES)
     public void playLottieAnimationWithResource_verifyFailureListener() {
         // fake the valid lottie image
         final int fakeValidResId = 111;
         doNothing().when(mAnimationView).setImageResource(fakeValidResId);
         doReturn(null).when(mAnimationView).getDrawable();
+        doNothing().when(mAnimationView).setAnimation(fakeValidResId);
 
         mPreference.setLottieAnimationResId(fakeValidResId);
         mPreference.onBindViewHolder(mViewHolder);
@@ -171,6 +184,50 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_AUTO_HIDE_EMPTY_LOTTIE_RES)
+    public void handleImageWithAnimation_emptyInputStreamDisabledFlag_verifyContainerVisible() {
+        doNothing().when(mAnimationView).setImageResource(111);
+        doReturn(null).when(mAnimationView).getDrawable();
+
+        mPreference.setLottieAnimationResId(111);
+        mPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(mAnimationView.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mIllustrationFrame.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUTO_HIDE_EMPTY_LOTTIE_RES)
+    public void handleImageWithAnimation_emptyInputStreamEnabledFlag_verifyContainerHidden() {
+        Resources res = spy(mContext.getResources());
+        doReturn(res).when(mAnimationView).getResources();
+        doReturn(new ByteArrayInputStream(new byte[] {})).when(res).openRawResource(111);
+
+        mPreference.setLottieAnimationResId(111);
+        mPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(mAnimationView.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mIllustrationFrame.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUTO_HIDE_EMPTY_LOTTIE_RES)
+    public void handleImageWithAnimation_nonEmptyInputStreamEnabledFlag_verifyContainerVisible() {
+        Resources res = spy(mContext.getResources());
+        doReturn(res).when(mAnimationView).getResources();
+        doReturn(new ByteArrayInputStream(new byte[] { 1, 2, 3 })).when(res).openRawResource(111);
+        doNothing().when(mAnimationView).setImageResource(111);
+        doNothing().when(mAnimationView).setAnimation(111);
+        doReturn(null).when(mAnimationView).getDrawable();
+
+        mPreference.setLottieAnimationResId(111);
+        mPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(mAnimationView.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mIllustrationFrame.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
     public void setMaxHeight_smallerThanRestrictedHeight_matchResult() {
         final int restrictedHeight =
                 mContext.getResources().getDimensionPixelSize(
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index be3f410..888e395 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -274,6 +274,9 @@
         Settings.Secure.SCREEN_RESOLUTION_MODE,
         Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS,
         Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
-        Settings.Secure.CHARGE_OPTIMIZATION_MODE
+        Settings.Secure.CHARGE_OPTIMIZATION_MODE,
+        Settings.Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS,
+        Settings.Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS,
+        Settings.Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS,
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index b1feede..b992ddc 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -18,6 +18,7 @@
 
 import static android.provider.settings.validators.SettingsValidators.ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.ANY_LONG_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.AUTOFILL_SERVICE_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
@@ -433,5 +434,8 @@
         VALIDATORS.put(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
                 new InclusiveIntegerRangeValidator(0, 10));
         VALIDATORS.put(Secure.CHARGE_OPTIMIZATION_MODE, new InclusiveIntegerRangeValidator(0, 10));
+        VALIDATORS.put(Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS, ANY_LONG_VALIDATOR);
+        VALIDATORS.put(Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS, ANY_LONG_VALIDATOR);
+        VALIDATORS.put(Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, NONE_NEGATIVE_LONG_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
index 677c81a..255b1ad 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
@@ -239,6 +239,18 @@
         }
     };
 
+    static final Validator ANY_LONG_VALIDATOR = value -> {
+        if (value == null) {
+            return true;
+        }
+        try {
+            Long.parseLong(value);
+            return true;
+        } catch (NumberFormatException e) {
+            return false;
+        }
+    };
+
     static final Validator CREDENTIAL_SERVICE_VALIDATOR = new Validator() {
         @Override
         public boolean validate(String value) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 461b6b3..70ce202 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2949,9 +2949,6 @@
         dumpSetting(s, p,
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
                 SystemSettingsProto.Screen.AUTO_BRIGHTNESS_ADJ);
-        dumpSetting(s, p,
-                Settings.System.SCREEN_BRIGHTNESS_FLOAT,
-                SystemSettingsProto.Screen.BRIGHTNESS_FLOAT);
         p.end(screenToken);
 
         dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index c891dfc..ab9a30b 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -233,6 +233,7 @@
                     Settings.Global.ENHANCED_4G_MODE_ENABLED,
                     Settings.Global.ENABLE_16K_PAGES, // Added for 16K developer option
                     Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES,
+                    Settings.Global.ERROR_KERNEL_LOG_PREFIX,
                     Settings.Global.ERROR_LOGCAT_PREFIX,
                     Settings.Global.EUICC_PROVISIONED,
                     Settings.Global.EUICC_SUPPORTED_COUNTRIES,
@@ -935,7 +936,6 @@
                         Settings.System.VOLUME_VOICE, // deprecated since API 2?
                         Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
                         Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only
-                        Settings.System.SCREEN_BRIGHTNESS_FLOAT,
                         Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
                         Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE,
                         Settings.System.WEAR_TTS_PREWARM_ENABLED,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 46bf494..374240b 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -347,6 +347,7 @@
     <uses-permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
     <uses-permission android:name="android.permission.USE_COMPANION_TRANSPORTS" />
     <uses-permission android:name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE" />
+    <uses-permission android:name="android.permission.DELIVER_COMPANION_MESSAGES" />
 
     <uses-permission android:name="android.permission.MANAGE_APPOPS" />
     <uses-permission android:name="android.permission.WATCH_APPOPS" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 65c5708..8b60ed0 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -78,11 +78,69 @@
     visibility: ["//visibility:private"],
 }
 
+filegroup {
+    name: "SystemUI-tests-broken-robofiles-run",
+    srcs: [
+        "tests/src/**/systemui/util/LifecycleFragmentTest.java",
+        "tests/src/**/systemui/util/TestableAlertDialogTest.kt",
+        "tests/src/**/systemui/util/kotlin/PairwiseFlowTest",
+        "tests/src/**/systemui/util/sensors/AsyncManagerTest.java",
+        "tests/src/**/systemui/util/sensors/ThresholdSensorImplTest.java",
+        "tests/src/**/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java",
+        "tests/src/**/systemui/statusbar/KeyboardShortcutListSearchTest.java",
+        "tests/src/**/systemui/statusbar/KeyboardShortcutsTest.java",
+        "tests/src/**/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt",
+        "tests/src/**/systemui/statusbar/notification/AssistantFeedbackControllerTest.java",
+        "tests/src/**/systemui/statusbar/notification/collection/NotifCollectionTest.java",
+        "tests/src/**/systemui/statusbar/notification/collection/NotificationEntryTest.java",
+        "tests/src/**/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt",
+        "tests/src/**/systemui/statusbar/notification/collection/ShadeListBuilderTest.java",
+        "tests/src/**/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java",
+        "tests/src/**/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java",
+        "tests/src/**/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt",
+        "tests/src/**/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt",
+        "tests/src/**/systemui/statusbar/NotificationLockscreenUserManagerTest.java",
+        "tests/src/**/systemui/statusbar/notification/logging/NotificationLoggerTest.java",
+        "tests/src/**/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java",
+        "tests/src/**/systemui/statusbar/notification/row/NotificationContentInflaterTest.java",
+        "tests/src/**/systemui/statusbar/notification/row/NotificationContentViewTest.kt",
+        "tests/src/**/systemui/statusbar/notification/row/NotificationConversationInfoTest.java",
+        "tests/src/**/systemui/statusbar/notification/row/NotificationGutsManagerTest.java",
+        "tests/src/**/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt",
+        "tests/src/**/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt",
+        "tests/src/**/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt",
+        "tests/src/**/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java",
+        "tests/src/**/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java",
+        "tests/src/**/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt",
+        "tests/src/**/systemui/statusbar/phone/AutoTileManagerTest.java",
+        "tests/src/**/systemui/statusbar/phone/CentralSurfacesImplTest.java",
+        "tests/src/**/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java",
+        "tests/src/**/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt",
+        "tests/src/**/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt",
+        "tests/src/**/systemui/statusbar/phone/PhoneStatusBarView.java",
+        "tests/src/**/systemui/statusbar/phone/PhoneStatusBarViewTest.kt",
+        "tests/src/**/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt",
+        "tests/src/**/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt",
+        "tests/src/**/systemui/statusbar/policy/CallbackControllerTest.java",
+        "tests/src/**/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java",
+        "tests/src/**/systemui/statusbar/policy/InflatedSmartRepliesTest.java",
+        "tests/src/**/systemui/statusbar/policy/LocationControllerImplTest.java",
+        "tests/src/**/systemui/statusbar/policy/RemoteInputViewTest.java",
+        "tests/src/**/systemui/statusbar/policy/SmartReplyViewTest.java",
+        "tests/src/**/systemui/statusbar/StatusBarStateControllerImplTest.kt",
+    ],
+}
+
 // We are running robolectric tests in the tests directory as well as
 // multivalent tests.  If you add a test, and it doesn't run in robolectric,
 // it should be added to this exclusion list. go/multivalent-tests
 filegroup {
-    name: "SystemUI-tests-broken-robofiles",
+    name: "SystemUI-tests-broken-robofiles-compile",
     srcs: [
         "tests/src/**/*DeviceOnlyTest.java",
         "tests/src/**/*DeviceOnlyTest.kt",
@@ -382,6 +440,7 @@
         "androidx.compose.material_material-icons-extended",
         "androidx.activity_activity-compose",
         "androidx.compose.animation_animation-graphics",
+        "device_policy_aconfig_flags_lib",
     ],
     libs: [
         "keepanno-annotations",
@@ -568,7 +627,7 @@
         "androidx.test.uiautomator_uiautomator",
         "androidx.core_core-animation-testing",
         "mockito-target-extended-minus-junit4",
-        "mockito-kotlin2",
+        "mockito-kotlin-nodeps",
         "androidx.test.ext.junit",
         "androidx.test.ext.truth",
         "kotlin-test",
@@ -622,6 +681,7 @@
         "//frameworks/libs/systemui:compilelib",
         "SystemUI-tests-base",
         "androidx.compose.runtime_runtime",
+        "SystemUI-core",
     ],
     libs: [
         "keepanno-annotations",
@@ -654,6 +714,7 @@
         "androidx.core_core-animation-testing",
         "androidx.test.ext.junit",
         "inline-mockito-robolectric-prebuilt",
+        "mockito-kotlin-nodeps",
         "platform-parametric-runner-lib",
         "SystemUICustomizationTestUtils",
         "kotlin-test",
@@ -700,7 +761,8 @@
         ":SystemUI-tests-robofiles",
     ],
     exclude_srcs: [
-        ":SystemUI-tests-broken-robofiles",
+        ":SystemUI-tests-broken-robofiles-compile",
+        ":SystemUI-tests-broken-robofiles-run",
     ],
     static_libs: [
         "RoboTestLibraries",
@@ -738,6 +800,7 @@
         "androidx.core_core-animation-testing",
         "androidx.test.ext.junit",
         "kosmos",
+        "mockito-kotlin-nodeps",
     ],
     libs: [
         "android.test.runner",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 59e2b91..9c58371 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -826,20 +826,6 @@
             </intent-filter>
         </activity>
 
-        <activity
-            android:name=".contrast.ContrastDialogActivity"
-            android:label="@string/quick_settings_contrast_label"
-            android:theme="@style/Theme.SystemUI.ContrastDialog"
-            android:finishOnCloseSystemDialogs="true"
-            android:launchMode="singleInstance"
-            android:excludeFromRecents="true"
-            android:exported="true">
-            <intent-filter>
-                <action android:name="com.android.intent.action.SHOW_CONTRAST_DIALOG" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </activity>
-
         <activity android:name=".ForegroundServicesDialog"
             android:process=":fgservices"
             android:excludeFromRecents="true"
@@ -1110,7 +1096,7 @@
                 android:resource="@xml/home_controls_dream_metadata" />
         </service>
 
-        <activity android:name="com.android.systemui.keyboard.shortcut.ShortcutHelperActivity"
+        <activity android:name="com.android.systemui.keyboard.shortcut.ui.view.ShortcutHelperActivity"
             android:exported="false"
             android:theme="@style/ShortcutHelperTheme"
             android:excludeFromRecents="true"
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 0c89a5d..deab818 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -59,13 +59,16 @@
       ]
     }
   ],
-  
+
   "auto-end-to-end-postsubmit": [
     {
       "name": "AndroidAutomotiveHomeTests",
       "options" : [
         {
           "include-filter": "android.platform.tests.HomeTest"
+        },
+        {
+          "exclude-filter": "android.platform.tests.HomeTest#testAssistantWidget"
         }
       ]
     },
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-cs/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-cs/strings.xml
index 15eb928..be1916f 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-cs/strings.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-cs/strings.xml
@@ -2,7 +2,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="accessibility_menu_service_name" msgid="730136711554740131">"Nabídka usnadnění přístupu"</string>
-    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Nabídka usnadnění přístupu zobrazuje na obrazovce velkou nabídku k ovládání zařízení. Můžete zamknout zařízení, upravit hlasitost a jas, pořídit snímek obrazovky apod."</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Nabídka usnadnění přístupu zobrazuje na obrazovce velkou nabídku k ovládání zařízení  – například k jeho zamknutí, úpravě hlasitosti a jasu, pořízení snímku obrazovky apod."</string>
     <string name="assistant_label" msgid="6796392082252272356">"Asistent"</string>
     <string name="assistant_utterance" msgid="65509599221141377">"Asistent"</string>
     <string name="a11y_settings_label" msgid="3977714687248445050">"Nastavení usnadnění přístupu"</string>
@@ -20,7 +20,7 @@
     <string name="brightness_down_label" msgid="7115662941913272072">"Snížit jas"</string>
     <string name="previous_button_content_description" msgid="840869171117765966">"Zpět na předchozí obrazovku"</string>
     <string name="next_button_content_description" msgid="6810058269847364406">"Přejít na další obrazovku"</string>
-    <string name="accessibility_menu_description" msgid="4458354794093858297">"Nabídka usnadnění přístupu zobrazuje na obrazovce velkou nabídku k ovládání zařízení. Můžete zamknout zařízení, upravit hlasitost a jas, pořídit snímek obrazovky apod."</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Nabídka usnadnění přístupu zobrazuje na obrazovce velkou nabídku k ovládání zařízení – například k jeho zamknutí, úpravě hlasitosti a jasu, pořízení snímku obrazovky apod."</string>
     <string name="accessibility_menu_summary" msgid="340071398148208130">"Ovládání zařízení pomocí velké nabídky"</string>
     <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Nastavení nabídky usnadnění přístupu"</string>
     <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Velká tlačítka"</string>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-eu/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-eu/strings.xml
index b314c8e..1e21c775 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-eu/strings.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-eu/strings.xml
@@ -20,7 +20,7 @@
     <string name="brightness_down_label" msgid="7115662941913272072">"Txikitu distira"</string>
     <string name="previous_button_content_description" msgid="840869171117765966">"Joan aurreko pantailara"</string>
     <string name="next_button_content_description" msgid="6810058269847364406">"Joan hurrengo pantailara"</string>
-    <string name="accessibility_menu_description" msgid="4458354794093858297">"Erabilerraztasun-menuari esker, tamaina handiko menu bat izango duzu pantailan; menu horren bidez, gailua kontrolatzeko aukera izango duzu. Besteak beste, hauek egin ahalko dituzu: gailua blokeatu; bolumena eta distira kontrolatu, eta pantaila-argazkiak egin."</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Erabilerraztasun-menuari esker, tamaina handiko menu bat izango duzu pantailan; menu horren bidez, gailua kontrolatzeko aukera izango duzu. Besteak beste, hauek egin ahalko dituzu: gailua blokeatu, bolumena eta distira kontrolatu, eta pantaila-argazkiak egin."</string>
     <string name="accessibility_menu_summary" msgid="340071398148208130">"Kontrolatu gailua menu handiaren bidez"</string>
     <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Erabilerraztasun-menuaren ezarpenak"</string>
     <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Botoi handiak"</string>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-hr/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-hr/strings.xml
index eff6cb4..5c3c99c 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-hr/strings.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-hr/strings.xml
@@ -2,7 +2,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="accessibility_menu_service_name" msgid="730136711554740131">"Izbornik pristupačnosti"</string>
-    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Izbornik pristupačnosti veliki je zaslonski izbornik koji vam omogućuje upravljanje uređajem. Putem ovog izbornika možete zaključati uređaj, upravljati glasnoćom i svjetlinom, izrađivati snimke zaslona i drugo."</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Izbornik pristupačnosti veliki je zaslonski izbornik koji vam omogućuje upravljanje uređajem. Putem njega možete zaključati uređaj, upravljati glasnoćom i svjetlinom, izrađivati snimke zaslona i drugo."</string>
     <string name="assistant_label" msgid="6796392082252272356">"Asistent"</string>
     <string name="assistant_utterance" msgid="65509599221141377">"Asistent"</string>
     <string name="a11y_settings_label" msgid="3977714687248445050">"Postavke pristupačnosti"</string>
@@ -20,7 +20,7 @@
     <string name="brightness_down_label" msgid="7115662941913272072">"Smanji svjetlinu"</string>
     <string name="previous_button_content_description" msgid="840869171117765966">"Idi na prethodni zaslon"</string>
     <string name="next_button_content_description" msgid="6810058269847364406">"Idi na sljedeći zaslon"</string>
-    <string name="accessibility_menu_description" msgid="4458354794093858297">"Izbornik pristupačnosti pruža velik izbornik na zaslonu u svrhu upravljanja uređajem. Možete zaključati uređaj, upravljati glasnoćom i svjetlinom, izrađivati snimke zaslona i drugo."</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Izbornik pristupačnosti veliki je zaslonski izbornik koji vam omogućuje upravljanje uređajem. Putem njega možete zaključati uređaj, upravljati glasnoćom i svjetlinom, izrađivati snimke zaslona i drugo."</string>
     <string name="accessibility_menu_summary" msgid="340071398148208130">"Upravljanje uređajem pomoću velikog izbornika"</string>
     <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Postavke izbornika pristupačnosti"</string>
     <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Veliki gumbi"</string>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-tr/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-tr/strings.xml
index 38cc395..d23ddca 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-tr/strings.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-tr/strings.xml
@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Erişilebilirlik menüsü"</string>
-    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Erişilebilirlik menüsü, cihazınızı kontrol etmeniz için geniş bir ekran menüsü sağlar. Cihazınızı kilitleyebilir, ses düzeyini ve parlaklığı kontrol edebilir, ekran görüntüsü alabilir ve daha fazlasını yapabilirsiniz."</string>
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Erişilebilirlik Menüsü"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Erişilebilirlik Menüsü, cihazınızı kontrol etmeniz için geniş bir ekran menüsü sağlar. Cihazınızı kilitleyebilir, ses düzeyini ve parlaklığı kontrol edebilir, ekran görüntüsü alabilir ve daha fazlasını yapabilirsiniz."</string>
     <string name="assistant_label" msgid="6796392082252272356">"Asistan"</string>
     <string name="assistant_utterance" msgid="65509599221141377">"Asistan"</string>
     <string name="a11y_settings_label" msgid="3977714687248445050">"Erişebilirlik Ayarları"</string>
@@ -20,7 +20,7 @@
     <string name="brightness_down_label" msgid="7115662941913272072">"Parlaklığı azalt"</string>
     <string name="previous_button_content_description" msgid="840869171117765966">"Önceki ekrana git"</string>
     <string name="next_button_content_description" msgid="6810058269847364406">"Sonraki ekrana git"</string>
-    <string name="accessibility_menu_description" msgid="4458354794093858297">"Erişilebilirlik menüsü, cihazınızı kontrol etmeniz için geniş bir ekran menüsü sağlar. Cihazınızı kilitleyebilir, ses düzeyini ve parlaklığı kontrol edebilir, ekran görüntüsü alabilir ve daha fazlasını yapabilirsiniz."</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Erişilebilirlik Menüsü, cihazınızı kontrol etmeniz için geniş bir ekran menüsü sağlar. Cihazınızı kilitleyebilir, ses düzeyini ve parlaklığı kontrol edebilir, ekran görüntüsü alabilir ve daha fazlasını yapabilirsiniz."</string>
     <string name="accessibility_menu_summary" msgid="340071398148208130">"Cihazı geniş menüyle kontrol edin"</string>
     <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Erişilebilirlik Menüsü Ayarları"</string>
     <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Büyük düğmeler"</string>
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 14ebc39..55edff6 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -4,6 +4,23 @@
 # NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
 
 flag {
+    name: "create_windowless_window_magnifier"
+    namespace: "accessibility"
+    description: "Uses SurfaceControlViewHost to create the magnifier for window magnification."
+    bug: "280992417"
+}
+
+flag {
+    name: "delay_show_magnification_button"
+    namespace: "accessibility"
+    description: "Delays the showing of magnification mode switch button."
+    bug: "338259519"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "floating_menu_animated_tuck"
     namespace: "accessibility"
     description: "Sets up animations for tucking/untucking and adjusts clipbounds."
@@ -56,8 +73,11 @@
 }
 
 flag {
-    name: "create_windowless_window_magnifier"
+    name: "save_and_restore_magnification_settings_buttons"
     namespace: "accessibility"
-    description: "Uses SurfaceControlViewHost to create the magnifier for window magnification."
-    bug: "280992417"
+    description: "Saves the selected button status in magnification settings and restore the status when revisiting the same smallest screen DP."
+    bug: "325567876"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
 }
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index fd90bd9..f3e2272 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -26,11 +26,10 @@
 }
 
 flag {
-
-    name: "notification_heads_up_cycling"
-    namespace: "systemui"
-    description: "Heads-up notification cycling animation for the Notification Avalanche feature."
-    bug: "316404716"
+   name: "priority_people_section"
+   namespace: "systemui"
+   description: "Add a new section for priority people (aka important conversations)."
+   bug: "340294566"
 }
 
 flag {
@@ -171,16 +170,6 @@
 }
 
 flag {
-    name: "nssl_falsing_fix"
-    namespace: "systemui"
-    description: "Minor touch changes to prevent falsing errors in NSSL"
-    bug: "316551193"
-    metadata {
-        purpose: PURPOSE_BUGFIX
-    }
-}
-
-flag {
     name: "refactor_get_current_user"
     namespace: "systemui"
     description: "KeyguardUpdateMonitor.getCurrentUser() was providing outdated results."
@@ -207,7 +196,16 @@
     description: "Re-enable the codepath that removed tinting of notifications when the"
         " standard background color is desired.  This was the behavior before we discovered"
         " a resources threading issue, which we worked around by tinting the notification"
-        " backgrounds and footer buttons."
+        " backgrounds."
+    bug: "294830092"
+}
+
+flag {
+    name: "notification_footer_background_tint_optimization"
+    namespace: "systemui"
+    description: "Remove duplicative tinting of notification footer buttons. This was the behavior"
+        " before we discovered a resources threading issue, which we worked around by applying the"
+        " same color as a tint to the background drawable of footer buttons."
     bug: "294830092"
 }
 
@@ -358,6 +356,14 @@
 }
 
 flag {
+    name: "status_bar_screen_sharing_chips"
+    namespace: "systemui"
+    description: "Show chips on the left side of the status bar when a user is screen sharing, "
+        "recording, or casting"
+    bug: "332662551"
+}
+
+flag {
     name: "compose_bouncer"
     namespace: "systemui"
     description: "Use the new compose bouncer in SystemUI"
@@ -408,6 +414,23 @@
 }
 
 flag {
+   name: "confine_notification_touch_to_view_width"
+   namespace: "systemui"
+   description: "Use notification view width when detecting gestures."
+   bug: "335828150"
+}
+
+flag {
+  name: "fix_image_wallpaper_crash_surface_already_released"
+  namespace: "systemui"
+  description: "Make sure ImageWallpaper doesn't return from OnSurfaceDestroyed until any drawing is finished"
+  bug: "337287154"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
    name: "activity_transition_use_largest_window"
    namespace: "systemui"
    description: "Target largest opening window during activity transitions."
@@ -480,6 +503,26 @@
 }
 
 flag {
+    name: "fix_screenshot_action_dismiss_system_windows"
+    namespace: "systemui"
+    description: "Dismiss existing system windows when starting action from screenshot UI"
+    bug: "309933761"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
+    name: "screenshot_scroll_crop_view_crash_fix"
+    namespace: "systemui"
+    description: "Mitigate crash on invalid computed range in CropView"
+    bug: "232633995"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "screenshot_private_profile_behavior_fix"
     namespace: "systemui"
     description: "Private profile support for screenshots"
@@ -560,6 +603,13 @@
 }
 
 flag {
+    name: "enable_contextual_tip_for_mute_volume"
+    namespace: "systemui"
+    description: "Enables the contextual tip for muting the volume."
+    bug: "337737048"
+}
+
+flag {
    name: "disable_contextual_tips_frequency_check"
    description: "Disables frequency capping check for contextual tips."
    namespace: "systemui"
@@ -581,6 +631,16 @@
 }
 
 flag {
+   name: "contextual_tips_assistant_dismiss_fix"
+   namespace: "systemui"
+   description: "Improve assistant dismiss signal accuracy for contextual tips."
+   bug: "334759504"
+   metadata {
+        purpose: PURPOSE_BUGFIX
+   }
+}
+
+flag {
    name: "shaderlib_loading_effect_refactor"
    namespace: "systemui"
    description: "Extend shader library to provide the common loading effects."
@@ -796,7 +856,7 @@
     name: "dream_input_session_pilfer_once"
     namespace: "systemui"
     description: "Pilfer at most once per input session"
-    bug: "324600132"
+    bug: "333596426"
     metadata {
       purpose: PURPOSE_BUGFIX
     }
@@ -837,6 +897,9 @@
     namespace: "systemui"
     description: "Enforce BaseUserRestriction for DISALLOW_CONFIG_BRIGHTNESS."
     bug: "329205638"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
 }
 
 flag {
@@ -850,6 +913,16 @@
 }
 
 flag {
+   name: "restart_dream_on_unocclude"
+   namespace: "systemui"
+   description: "re-enters dreaming upon unocclude when dreaming when originally occluding"
+   bug: "338051457"
+   metadata {
+     purpose: PURPOSE_BUGFIX
+   }
+}
+
+flag {
   name: "communal_bouncer_do_not_modify_plugin_open"
   namespace: "systemui"
   description: "do not modify notification shade when handling bouncer expansion."
@@ -858,3 +931,57 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "app_clips_backlinks"
+  namespace: "systemui"
+  description: "Enables Backlinks improvement feature in App Clips"
+  bug: "300307759"
+}
+
+flag {
+  name: "qs_custom_tile_click_guaranteed_bug_fix"
+  namespace: "systemui"
+  description: "Guarantee that clicks on a tile always happen by postponing onStopListening until after the click."
+  bug: "339290820"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
+  name: "media_controls_user_initiated_dismiss"
+  namespace: "systemui"
+  description: "Only dismiss media notifications when the control was removed by the user."
+  bug: "335875159"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
+  name: "validate_keyboard_shortcut_helper_icon_uri"
+  namespace: "systemui"
+  description: "Adds a check that the caller can access the content URI of an icon in the shortcut helper."
+  bug: "331180422"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
+  name: "glanceable_hub_gesture_handle"
+  namespace: "systemui"
+  description: "Shows a vertical bar at the right edge to indicate the user can swipe to open the glanceable hub"
+  bug: "339667383"
+}
+
+flag {
+   name: "register_wallpaper_notifier_background"
+   namespace: "systemui"
+   description: "Decide whether to register wallpaper change broadcast receiver on background executor."
+   bug: "327315860"
+   metadata {
+     purpose: PURPOSE_BUGFIX
+   }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index 1e60b98..23df26f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -23,6 +23,7 @@
 import android.graphics.Matrix
 import android.graphics.Rect
 import android.graphics.RectF
+import android.os.Binder
 import android.os.Build
 import android.os.Handler
 import android.os.Looper
@@ -36,7 +37,11 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.view.WindowManager.TRANSIT_TO_BACK
 import android.view.animation.PathInterpolator
+import android.window.RemoteTransition
+import android.window.TransitionFilter
 import androidx.annotation.AnyThread
 import androidx.annotation.BinderThread
 import androidx.annotation.UiThread
@@ -44,6 +49,10 @@
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.policy.ScreenDecorationsUtils
 import com.android.systemui.Flags.activityTransitionUseLargestWindow
+import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
+import com.android.wm.shell.shared.IShellTransitions
+import com.android.wm.shell.shared.ShellTransitions
+import java.util.concurrent.Executor
 import kotlin.math.roundToInt
 
 private const val TAG = "ActivityTransitionAnimator"
@@ -52,14 +61,22 @@
  * A class that allows activities to be started in a seamless way from a view that is transforming
  * nicely into the starting window.
  */
-class ActivityTransitionAnimator(
+class ActivityTransitionAnimator
+@JvmOverloads
+constructor(
+    /** The executor that runs on the main thread. */
+    private val mainExecutor: Executor,
+
+    /** The object used to register ephemeral returns and long-lived transitions. */
+    private val transitionRegister: TransitionRegister? = null,
+
     /** The animator used when animating a View into an app. */
-    private val transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
+    private val transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor),
 
     /** The animator used when animating a Dialog into an app. */
     // TODO(b/218989950): Remove this animator and instead set the duration of the dim fade out to
     // TIMINGS.contentBeforeFadeOutDuration.
-    private val dialogToAppAnimator: TransitionAnimator = DEFAULT_DIALOG_TO_APP_ANIMATOR,
+    private val dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor),
 
     /**
      * Whether we should disable the WindowManager timeout. This should be set to true in tests
@@ -68,6 +85,36 @@
     // TODO(b/301385865): Remove this flag.
     private val disableWmTimeout: Boolean = false,
 ) {
+    @JvmOverloads
+    constructor(
+        mainExecutor: Executor,
+        shellTransitions: ShellTransitions,
+        transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor),
+        dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor),
+        disableWmTimeout: Boolean = false,
+    ) : this(
+        mainExecutor,
+        TransitionRegister.fromShellTransitions(shellTransitions),
+        transitionAnimator,
+        dialogToAppAnimator,
+        disableWmTimeout,
+    )
+
+    @JvmOverloads
+    constructor(
+        mainExecutor: Executor,
+        iShellTransitions: IShellTransitions,
+        transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor),
+        dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor),
+        disableWmTimeout: Boolean = false,
+    ) : this(
+        mainExecutor,
+        TransitionRegister.fromIShellTransitions(iShellTransitions),
+        transitionAnimator,
+        dialogToAppAnimator,
+        disableWmTimeout,
+    )
+
     companion object {
         /** The timings when animating a View into an app. */
         @JvmField
@@ -100,10 +147,6 @@
         // TODO(b/288507023): Remove this flag.
         @JvmField val DEBUG_TRANSITION_ANIMATION = Build.IS_DEBUGGABLE
 
-        private val DEFAULT_TRANSITION_ANIMATOR = TransitionAnimator(TIMINGS, INTERPOLATORS)
-        private val DEFAULT_DIALOG_TO_APP_ANIMATOR =
-            TransitionAnimator(DIALOG_TIMINGS, INTERPOLATORS)
-
         /** Durations & interpolators for the navigation bar fading in & out. */
         private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
         private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L
@@ -121,6 +164,14 @@
          * cancelled by WM.
          */
         private const val LONG_TRANSITION_TIMEOUT = 5_000L
+
+        private fun defaultTransitionAnimator(mainExecutor: Executor): TransitionAnimator {
+            return TransitionAnimator(mainExecutor, TIMINGS, INTERPOLATORS)
+        }
+
+        private fun defaultDialogToAppAnimator(mainExecutor: Executor): TransitionAnimator {
+            return TransitionAnimator(mainExecutor, DIALOG_TIMINGS, INTERPOLATORS)
+        }
     }
 
     /**
@@ -223,6 +274,10 @@
             }
         }
 
+        if (animationAdapter != null && controller.transitionCookie != null) {
+            registerEphemeralReturnAnimation(controller, transitionRegister)
+        }
+
         val launchResult = intentStarter(animationAdapter)
 
         // Only animate if the app is not already on top and will be opened, unless we are on the
@@ -257,9 +312,7 @@
 
     private fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
         if (Looper.myLooper() != Looper.getMainLooper()) {
-            this.transitionContainer.context.mainExecutor.execute {
-                callOnIntentStartedOnMainThread(willAnimate)
-            }
+            mainExecutor.execute { callOnIntentStartedOnMainThread(willAnimate) }
         } else {
             if (DEBUG_TRANSITION_ANIMATION) {
                 Log.d(
@@ -294,6 +347,66 @@
         }
     }
 
+    /**
+     * Uses [transitionRegister] to set up the return animation for the given [launchController].
+     *
+     * De-registration is set up automatically once the return animation is run.
+     *
+     * TODO(b/339194555): automatically de-register when the launchable is detached.
+     */
+    private fun registerEphemeralReturnAnimation(
+        launchController: Controller,
+        transitionRegister: TransitionRegister?
+    ) {
+        if (!returnAnimationFrameworkLibrary()) return
+
+        var cleanUpRunnable: Runnable? = null
+        val returnRunner =
+            createRunner(
+                object : DelegateTransitionAnimatorController(launchController) {
+                    override val isLaunching = false
+
+                    override fun onTransitionAnimationCancelled(
+                        newKeyguardOccludedState: Boolean?
+                    ) {
+                        super.onTransitionAnimationCancelled(newKeyguardOccludedState)
+                        cleanUp()
+                    }
+
+                    override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+                        super.onTransitionAnimationEnd(isExpandingFullyAbove)
+                        cleanUp()
+                    }
+
+                    private fun cleanUp() {
+                        cleanUpRunnable?.run()
+                    }
+                }
+            )
+
+        // mTypeSet and mModes match back signals only, and not home. This is on purpose, because
+        // we only want ephemeral return animations triggered in these scenarios.
+        val filter =
+            TransitionFilter().apply {
+                mTypeSet = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK)
+                mRequirements =
+                    arrayOf(
+                        TransitionFilter.Requirement().apply {
+                            mLaunchCookie = launchController.transitionCookie
+                            mModes = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK)
+                        }
+                    )
+            }
+        val transition =
+            RemoteTransition(
+                RemoteAnimationRunnerCompat.wrap(returnRunner),
+                "${launchController.transitionCookie}_returnTransition"
+            )
+
+        transitionRegister?.register(filter, transition)
+        cleanUpRunnable = Runnable { transitionRegister?.unregister(transition) }
+    }
+
     /** Add a [Listener] that can listen to transition animations. */
     fun addListener(listener: Listener) {
         listeners.add(listener)
@@ -378,8 +491,14 @@
              * Note: The background of [view] should be a (rounded) rectangle so that it can be
              * properly animated.
              */
+            @JvmOverloads
             @JvmStatic
-            fun fromView(view: View, cujType: Int? = null): Controller? {
+            fun fromView(
+                view: View,
+                cujType: Int? = null,
+                cookie: TransitionCookie? = null,
+                returnCujType: Int? = null
+            ): Controller? {
                 // Make sure the View we launch from implements LaunchableView to avoid visibility
                 // issues.
                 if (view !is LaunchableView) {
@@ -400,7 +519,7 @@
                     return null
                 }
 
-                return GhostedViewTransitionAnimatorController(view, cujType)
+                return GhostedViewTransitionAnimatorController(view, cujType, cookie, returnCujType)
             }
         }
 
@@ -424,6 +543,17 @@
             get() = false
 
         /**
+         * The cookie associated with the transition controlled by this [Controller].
+         *
+         * This should be defined for all return [Controller] (when [isLaunching] is false) and for
+         * their associated launch [Controller]s.
+         *
+         * For the recommended format, see [TransitionCookie].
+         */
+        val transitionCookie: TransitionCookie?
+            get() = null
+
+        /**
          * The intent was started. If [willAnimate] is false, nothing else will happen and the
          * animation will not be started.
          */
@@ -479,12 +609,10 @@
         controller: Controller,
         callback: Callback,
         /** The animator to use to animate the window transition. */
-        transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
+        transitionAnimator: TransitionAnimator,
         /** Listener for animation lifecycle events. */
         listener: Listener? = null
     ) : IRemoteAnimationRunner.Stub() {
-        private val context = controller.transitionContainer.context
-
         // This is being passed across IPC boundaries and cycles (through PendingIntentRecords,
         // etc.) are possible. So we need to make sure we drop any references that might
         // transitively cause leaks when we're done with animation.
@@ -493,11 +621,12 @@
         init {
             delegate =
                 AnimationDelegate(
+                    mainExecutor,
                     controller,
                     callback,
                     DelegatingAnimationCompletionListener(listener, this::dispose),
                     transitionAnimator,
-                    disableWmTimeout
+                    disableWmTimeout,
                 )
         }
 
@@ -510,7 +639,7 @@
             finishedCallback: IRemoteAnimationFinishedCallback?
         ) {
             val delegate = delegate
-            context.mainExecutor.execute {
+            mainExecutor.execute {
                 if (delegate == null) {
                     Log.i(TAG, "onAnimationStart called after completion")
                     // Animation started too late and timed out already. We need to still
@@ -525,7 +654,7 @@
         @BinderThread
         override fun onAnimationCancelled() {
             val delegate = delegate
-            context.mainExecutor.execute {
+            mainExecutor.execute {
                 delegate ?: Log.wtf(TAG, "onAnimationCancelled called after completion")
                 delegate?.onAnimationCancelled()
             }
@@ -535,19 +664,21 @@
         fun dispose() {
             // Drop references to animation controller once we're done with the animation
             // to avoid leaking.
-            context.mainExecutor.execute { delegate = null }
+            mainExecutor.execute { delegate = null }
         }
     }
 
     class AnimationDelegate
     @JvmOverloads
     constructor(
+        private val mainExecutor: Executor,
         private val controller: Controller,
         private val callback: Callback,
         /** Listener for animation lifecycle events. */
         private val listener: Listener? = null,
         /** The animator to use to animate the window transition. */
-        private val transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
+        private val transitionAnimator: TransitionAnimator =
+            defaultTransitionAnimator(mainExecutor),
 
         /**
          * Whether we should disable the WindowManager timeout. This should be set to true in tests
@@ -643,7 +774,7 @@
                 return
             }
 
-            val window = findRootTaskIfPossible(apps)
+            val window = findTargetWindowIfPossible(apps)
             if (window == null) {
                 Log.i(TAG, "Aborting the animation as no window is opening")
                 callback?.invoke()
@@ -667,7 +798,7 @@
             startAnimation(window, navigationBar, callback)
         }
 
-        private fun findRootTaskIfPossible(
+        private fun findTargetWindowIfPossible(
             apps: Array<out RemoteAnimationTarget>?
         ): RemoteAnimationTarget? {
             if (apps == null) {
@@ -685,6 +816,19 @@
             for (it in apps) {
                 if (it.mode == targetMode) {
                     if (activityTransitionUseLargestWindow()) {
+                        if (returnAnimationFrameworkLibrary()) {
+                            // If the controller contains a cookie, _only_ match if the candidate
+                            // contains the matching cookie.
+                            if (
+                                controller.transitionCookie != null &&
+                                    it.taskInfo
+                                        ?.launchCookies
+                                        ?.contains(controller.transitionCookie) != true
+                            ) {
+                                continue
+                            }
+                        }
+
                         if (
                             candidate == null ||
                                 !it.hasAnimatingParent && candidate.hasAnimatingParent
@@ -797,11 +941,7 @@
                         progress: Float,
                         linearProgress: Float
                     ) {
-                        // Apply the state to the window only if it is visible, i.e. when the
-                        // expanding view is *not* visible.
-                        if (!state.visible) {
-                            applyStateToWindow(window, state, linearProgress)
-                        }
+                        applyStateToWindow(window, state, linearProgress)
                         navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
 
                         listener?.onTransitionAnimationProgress(linearProgress)
@@ -1039,4 +1179,72 @@
             return (this.width() * this.height()) > (other.width() * other.height())
         }
     }
+
+    /**
+     * Wraps one of the two methods we have to register remote transitions with WM Shell:
+     * - for in-process registrations (e.g. System UI) we use [ShellTransitions]
+     * - for cross-process registrations (e.g. Launcher) we use [IShellTransitions]
+     *
+     * Important: each instance of this class must wrap exactly one of the two.
+     */
+    class TransitionRegister
+    private constructor(
+        private val shellTransitions: ShellTransitions? = null,
+        private val iShellTransitions: IShellTransitions? = null,
+    ) {
+        init {
+            assert((shellTransitions != null).xor(iShellTransitions != null))
+        }
+
+        companion object {
+            /** Provides a [TransitionRegister] instance wrapping [ShellTransitions]. */
+            fun fromShellTransitions(shellTransitions: ShellTransitions): TransitionRegister {
+                return TransitionRegister(shellTransitions = shellTransitions)
+            }
+
+            /** Provides a [TransitionRegister] instance wrapping [IShellTransitions]. */
+            fun fromIShellTransitions(iShellTransitions: IShellTransitions): TransitionRegister {
+                return TransitionRegister(iShellTransitions = iShellTransitions)
+            }
+        }
+
+        /** Register [remoteTransition] with WM Shell using the given [filter]. */
+        internal fun register(
+            filter: TransitionFilter,
+            remoteTransition: RemoteTransition,
+        ) {
+            shellTransitions?.registerRemote(filter, remoteTransition)
+            iShellTransitions?.registerRemote(filter, remoteTransition)
+        }
+
+        /** Unregister [remoteTransition] from WM Shell. */
+        internal fun unregister(remoteTransition: RemoteTransition) {
+            shellTransitions?.unregisterRemote(remoteTransition)
+            iShellTransitions?.unregisterRemote(remoteTransition)
+        }
+    }
+
+    /**
+     * A cookie used to uniquely identify a task launched using an
+     * [ActivityTransitionAnimator.Controller].
+     *
+     * The [String] encapsulated by this class should be formatted in such a way to be unique across
+     * the system, but reliably constant for the same associated launchable.
+     *
+     * Recommended naming scheme:
+     * - DO use the fully qualified name of the class that owns the instance of the launchable,
+     *   along with a concise and precise description of the purpose of the launchable in question.
+     * - DO NOT introduce uniqueness through the use of timestamps or other runtime variables that
+     *   will change if the instance is destroyed and re-created.
+     *
+     * Example: "com.not.the.real.class.name.ShadeController_openSettingsButton"
+     *
+     * Note that sometimes (e.g. in recycler views) there could be multiple instances of the same
+     * launchable, and no static knowledge to adequately differentiate between them using a single
+     * description. In this case, the recommendation is to append a unique identifier related to the
+     * contents of the launchable.
+     *
+     * Example: “com.not.the.real.class.name.ToastWebResult_launchAga_id143256”
+     */
+    data class TransitionCookie(private val cookie: String) : Binder()
 }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
index b89ebfc..f5d01d7 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
@@ -37,6 +37,7 @@
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.util.maybeForceFullscreen
 import com.android.systemui.util.registerAnimationOnBackInvoked
+import java.util.concurrent.Executor
 import kotlin.math.roundToInt
 
 private const val TAG = "DialogTransitionAnimator"
@@ -55,10 +56,16 @@
 class DialogTransitionAnimator
 @JvmOverloads
 constructor(
+    private val mainExecutor: Executor,
     private val callback: Callback,
     private val interactionJankMonitor: InteractionJankMonitor,
     private val featureFlags: AnimationFeatureFlags,
-    private val transitionAnimator: TransitionAnimator = TransitionAnimator(TIMINGS, INTERPOLATORS),
+    private val transitionAnimator: TransitionAnimator =
+        TransitionAnimator(
+            mainExecutor,
+            TIMINGS,
+            INTERPOLATORS,
+        ),
     private val isForTesting: Boolean = false,
 ) {
     private companion object {
@@ -937,24 +944,9 @@
                 }
 
                 override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
-                    // onLaunchAnimationEnd is called by an Animator at the end of the animation,
-                    // on a Choreographer animation tick. The following calls will move the animated
-                    // content from the dialog overlay back to its original position, and this
-                    // change must be reflected in the next frame given that we then sync the next
-                    // frame of both the content and dialog ViewRoots. However, in case that content
-                    // is rendered by Compose, whose compositions are also scheduled on a
-                    // Choreographer frame, any state change made *right now* won't be reflected in
-                    // the next frame given that a Choreographer frame can't schedule another and
-                    // have it happen in the same frame. So we post the forwarded calls to
-                    // [Controller.onLaunchAnimationEnd], leaving this Choreographer frame, ensuring
-                    // that the move of the content back to its original window will be reflected in
-                    // the next frame right after [onLaunchAnimationEnd] is called.
-                    dialog.context.mainExecutor.execute {
-                        startController.onTransitionAnimationEnd(isExpandingFullyAbove)
-                        endController.onTransitionAnimationEnd(isExpandingFullyAbove)
-
-                        onLaunchAnimationEnd()
-                    }
+                    startController.onTransitionAnimationEnd(isExpandingFullyAbove)
+                    endController.onTransitionAnimationEnd(isExpandingFullyAbove)
+                    onLaunchAnimationEnd()
                 }
 
                 override fun onTransitionAnimationProgress(
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
index e4bb2ad..21557b8 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
@@ -25,10 +25,30 @@
      * [Expandable] into an Activity, or return `null` if this [Expandable] should not be animated
      * (e.g. if it is currently not attached or visible).
      *
-     * @param cujType the CUJ type from the [com.android.internal.jank.InteractionJankMonitor]
+     * @param launchCujType The CUJ type from the [com.android.internal.jank.InteractionJankMonitor]
      *   associated to the launch that will use this controller.
+     * @param cookie The unique cookie associated with the launch that will use this controller.
+     *   This is required iff the a return animation should be included.
+     * @param returnCujType The CUJ type from the [com.android.internal.jank.InteractionJankMonitor]
+     *   associated to the return animation that will use this controller.
      */
-    fun activityTransitionController(cujType: Int? = null): ActivityTransitionAnimator.Controller?
+    fun activityTransitionController(
+        launchCujType: Int? = null,
+        cookie: ActivityTransitionAnimator.TransitionCookie? = null,
+        returnCujType: Int? = null
+    ): ActivityTransitionAnimator.Controller?
+
+    /**
+     * See [activityTransitionController] above.
+     *
+     * Interfaces don't support [JvmOverloads], so this is a useful overload for Java usages that
+     * don't use the return-related parameters.
+     */
+    fun activityTransitionController(
+        launchCujType: Int? = null
+    ): ActivityTransitionAnimator.Controller? {
+        return activityTransitionController(launchCujType, cookie = null, returnCujType = null)
+    }
 
     /**
      * Create a [DialogTransitionAnimator.Controller] that can be used to expand this [Expandable]
@@ -48,9 +68,16 @@
         fun fromView(view: View): Expandable {
             return object : Expandable {
                 override fun activityTransitionController(
-                    cujType: Int?,
+                    launchCujType: Int?,
+                    cookie: ActivityTransitionAnimator.TransitionCookie?,
+                    returnCujType: Int?
                 ): ActivityTransitionAnimator.Controller? {
-                    return ActivityTransitionAnimator.Controller.fromView(view, cujType)
+                    return ActivityTransitionAnimator.Controller.fromView(
+                        view,
+                        launchCujType,
+                        cookie,
+                        returnCujType
+                    )
                 }
 
                 override fun dialogTransitionController(
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index fd79f62..9d45073 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -59,8 +59,12 @@
     /** The view that will be ghosted and from which the background will be extracted. */
     private val ghostedView: View,
 
-    /** The [CujType] associated to this animation. */
-    private val cujType: Int? = null,
+    /** The [CujType] associated to this launch animation. */
+    private val launchCujType: Int? = null,
+    override val transitionCookie: ActivityTransitionAnimator.TransitionCookie? = null,
+
+    /** The [CujType] associated to this return animation. */
+    private val returnCujType: Int? = null,
     private var interactionJankMonitor: InteractionJankMonitor =
         InteractionJankMonitor.getInstance(),
 ) : ActivityTransitionAnimator.Controller {
@@ -104,6 +108,15 @@
      */
     private val background: Drawable?
 
+    /** CUJ identifier accounting for whether this controller is for a launch or a return. */
+    private val cujType: Int?
+        get() =
+            if (isLaunching) {
+                launchCujType
+            } else {
+                returnCujType
+            }
+
     init {
         // Make sure the View we launch from implements LaunchableView to avoid visibility issues.
         if (ghostedView !is LaunchableView) {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index 679c969..cc55df1 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -31,12 +31,17 @@
 import androidx.annotation.VisibleForTesting
 import com.android.app.animation.Interpolators.LINEAR
 import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
+import java.util.concurrent.Executor
 import kotlin.math.roundToInt
 
 private const val TAG = "TransitionAnimator"
 
 /** A base class to animate a window (activity or dialog) launch to or return from a view . */
-class TransitionAnimator(private val timings: Timings, private val interpolators: Interpolators) {
+class TransitionAnimator(
+    private val mainExecutor: Executor,
+    private val timings: Timings,
+    private val interpolators: Interpolators,
+) {
     companion object {
         internal const val DEBUG = false
         private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
@@ -351,11 +356,27 @@
                     if (DEBUG) {
                         Log.d(TAG, "Animation ended")
                     }
-                    controller.onTransitionAnimationEnd(isExpandingFullyAbove)
-                    transitionContainerOverlay.remove(windowBackgroundLayer)
 
-                    if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
-                        openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
+                    // onAnimationEnd is called at the end of the animation, on a Choreographer
+                    // animation tick. During dialog launches, the following calls will move the
+                    // animated content from the dialog overlay back to its original position, and
+                    // this change must be reflected in the next frame given that we then sync the
+                    // next frame of both the content and dialog ViewRoots. During SysUI activity
+                    // launches, we will instantly collapse the shade at the end of the transition.
+                    // However, if those are rendered by Compose, whose compositions are also
+                    // scheduled on a Choreographer frame, any state change made *right now* won't
+                    // be reflected in the next frame given that a Choreographer frame can't
+                    // schedule another and have it happen in the same frame. So we post the
+                    // forwarded calls to [Controller.onLaunchAnimationEnd] in the main executor,
+                    // leaving this Choreographer frame, ensuring that any state change applied by
+                    // onTransitionAnimationEnd() will be reflected in the same frame.
+                    mainExecutor.execute {
+                        controller.onTransitionAnimationEnd(isExpandingFullyAbove)
+                        transitionContainerOverlay.remove(windowBackgroundLayer)
+
+                        if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
+                            openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
+                        }
                     }
                 }
             }
diff --git a/packages/SystemUI/checks/Android.bp b/packages/SystemUI/checks/Android.bp
index addcaf4..04ac748 100644
--- a/packages/SystemUI/checks/Android.bp
+++ b/packages/SystemUI/checks/Android.bp
@@ -38,8 +38,9 @@
     defaults: ["AndroidLintCheckerTestDefaults"],
     srcs: ["tests/**/*.kt"],
     data: [
-        ":framework",
         ":androidx.annotation_annotation",
+        ":dagger2",
+        ":framework",
         ":kotlinx-coroutines-core",
     ],
     static_libs: [
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CollectAsStateDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CollectAsStateDetector.kt
new file mode 100644
index 0000000..94620c4
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CollectAsStateDetector.kt
@@ -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 com.android.internal.systemui.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UFile
+import org.jetbrains.uast.UImportStatement
+
+class CollectAsStateDetector : Detector(), SourceCodeScanner {
+
+    override fun getApplicableUastTypes(): List<Class<out UElement>> {
+        return listOf(UFile::class.java)
+    }
+
+    override fun createUastHandler(context: JavaContext): UElementHandler {
+        return object : UElementHandler() {
+            override fun visitFile(node: UFile) {
+                node.imports.forEach { importStatement ->
+                    visitImportStatement(context, importStatement)
+                }
+            }
+        }
+    }
+
+    private fun visitImportStatement(
+        context: JavaContext,
+        importStatement: UImportStatement,
+    ) {
+        val importText = importStatement.importReference?.asSourceString() ?: return
+        if (ILLEGAL_IMPORT == importText) {
+            context.report(
+                issue = ISSUE,
+                scope = importStatement,
+                location = context.getLocation(importStatement),
+                message = "collectAsState considered harmful",
+            )
+        }
+    }
+
+    companion object {
+        @JvmField
+        val ISSUE =
+            Issue.create(
+                id = "OverlyEagerCollectAsState",
+                briefDescription = "collectAsState considered harmful",
+                explanation =
+                    """
+                go/sysui-compose#collect-as-state
+
+                Don't use collectAsState as it will set up a coroutine that keeps collecting from a
+                flow until its coroutine scope becomes inactive. This prevents the work from being
+                properly paused while the surrounding lifecycle becomes paused or stopped and is
+                therefore considered harmful.
+
+                Instead, use Flow.collectAsStateWithLifecycle(initial: T) or
+                StateFlow.collectAsStateWithLifecycle(). These APIs correctly pause the collection
+                coroutine while the lifecycle drops below the specified minActiveState (which
+                defaults to STARTED meaning that it will pause when the Compose-hosting window
+                becomes invisible).
+            """
+                        .trimIndent(),
+                category = Category.PERFORMANCE,
+                priority = 8,
+                severity = Severity.ERROR,
+                implementation =
+                    Implementation(
+                        CollectAsStateDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE,
+                    ),
+            )
+
+        private val ILLEGAL_IMPORT = "androidx.compose.runtime.collectAsState"
+    }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SingletonAndroidComponentDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SingletonAndroidComponentDetector.kt
new file mode 100644
index 0000000..68ec1ee
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SingletonAndroidComponentDetector.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.AnnotationInfo
+import com.android.tools.lint.detector.api.AnnotationUsageInfo
+import com.android.tools.lint.detector.api.AnnotationUsageType
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import org.jetbrains.uast.UAnnotation
+import org.jetbrains.uast.UClass
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UMethod
+
+/**
+ * Prevents binding Activities, Services, and BroadcastReceivers as Singletons in the Dagger graph.
+ *
+ * It is OK to mark a BroadcastReceiver as singleton as long as it is being constructed/injected and
+ * registered directly in the code. If instead it is declared in the manifest, and we let Android
+ * construct it for us, we also need to let Android destroy it for us, so don't allow marking it as
+ * singleton.
+ */
+class SingletonAndroidComponentDetector : Detector(), SourceCodeScanner {
+    override fun applicableAnnotations(): List<String> {
+        return listOf(
+            "com.android.systemui.dagger.SysUISingleton",
+        )
+    }
+
+    override fun isApplicableAnnotationUsage(type: AnnotationUsageType): Boolean =
+        type == AnnotationUsageType.DEFINITION
+
+    override fun visitAnnotationUsage(
+        context: JavaContext,
+        element: UElement,
+        annotationInfo: AnnotationInfo,
+        usageInfo: AnnotationUsageInfo
+    ) {
+        if (element !is UAnnotation) {
+            return
+        }
+
+        val parent = element.uastParent ?: return
+
+        if (isInvalidBindingMethod(parent)) {
+            context.report(
+                ISSUE,
+                element,
+                context.getLocation(element),
+                "Do not bind Activities, Services, or BroadcastReceivers as Singleton."
+            )
+        } else if (isInvalidClassDeclaration(parent)) {
+            context.report(
+                ISSUE,
+                element,
+                context.getLocation(element),
+                "Do not mark Activities or Services as Singleton."
+            )
+        }
+    }
+
+    private fun isInvalidBindingMethod(parent: UElement): Boolean {
+        if (parent !is UMethod) {
+            return false
+        }
+
+        if (
+            parent.returnType?.canonicalText !in
+                listOf(
+                    "android.app.Activity",
+                    "android.app.Service",
+                    "android.content.BroadcastReceiver",
+                )
+        ) {
+            return false
+        }
+
+        if (
+            !MULTIBIND_ANNOTATIONS.all { it in parent.annotations.map { it.qualifiedName } } &&
+                !MULTIPROVIDE_ANNOTATIONS.all { it in parent.annotations.map { it.qualifiedName } }
+        ) {
+            return false
+        }
+        return true
+    }
+
+    private fun isInvalidClassDeclaration(parent: UElement): Boolean {
+        if (parent !is UClass) {
+            return false
+        }
+
+        if (
+            parent.javaPsi.superClass?.qualifiedName !in
+                listOf(
+                    "android.app.Activity",
+                    "android.app.Service",
+                    // Fine to mark BroadcastReceiver as singleton in this scenario
+                )
+        ) {
+            return false
+        }
+
+        return true
+    }
+
+    companion object {
+        @JvmField
+        val ISSUE: Issue =
+            Issue.create(
+                id = "SingletonAndroidComponent",
+                briefDescription = "Activity, Service, or BroadcastReceiver marked as Singleton",
+                explanation =
+                    """Activities, Services, and BroadcastReceivers are created and destroyed by
+                        the Android System Server. Marking them with a Dagger scope
+                        results in them being cached and reused by Dagger. Trying to reuse a
+                        component like this will make for a very bad time.""",
+                category = Category.CORRECTNESS,
+                priority = 10,
+                severity = Severity.ERROR,
+                moreInfo =
+                    "https://developer.android.com/guide/components/activities/process-lifecycle",
+                // Note that JAVA_FILE_SCOPE also includes Kotlin source files.
+                implementation =
+                    Implementation(
+                        SingletonAndroidComponentDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                    )
+            )
+
+        private val MULTIBIND_ANNOTATIONS =
+            listOf("dagger.Binds", "dagger.multibindings.IntoMap", "dagger.multibindings.ClassKey")
+
+        val MULTIPROVIDE_ANNOTATIONS =
+            listOf(
+                "dagger.Provides",
+                "dagger.multibindings.IntoMap",
+                "dagger.multibindings.ClassKey"
+            )
+    }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index e93264c..73ac6cc 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -32,6 +32,7 @@
                 BindServiceOnMainThreadDetector.ISSUE,
                 BroadcastSentViaContextDetector.ISSUE,
                 CleanArchitectureDependencyViolationDetector.ISSUE,
+                CollectAsStateDetector.ISSUE,
                 DumpableNotRegisteredDetector.ISSUE,
                 FlowDetector.SHARED_FLOW_CREATION,
                 SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY,
@@ -40,6 +41,7 @@
                 RegisterReceiverViaContextDetector.ISSUE,
                 SoftwareBitmapDetector.ISSUE,
                 NonInjectedServiceDetector.ISSUE,
+                SingletonAndroidComponentDetector.ISSUE,
                 StaticSettingsProviderDetector.ISSUE,
                 DemotingTestWithoutBugDetector.ISSUE,
                 TestFunctionNameViolationDetector.ISSUE,
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
index e1cca88..8396f3f 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
@@ -21,8 +21,9 @@
 
 internal val libraryNames =
     arrayOf(
-        "framework.jar",
         "androidx.annotation_annotation.jar",
+        "dagger2.jar",
+        "framework.jar",
         "kotlinx-coroutines-core.jar",
     )
 
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CollectAsStateDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CollectAsStateDetectorTest.kt
new file mode 100644
index 0000000..6962b4e
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CollectAsStateDetectorTest.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.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class CollectAsStateDetectorTest : SystemUILintDetectorTest() {
+
+    override fun getDetector(): Detector {
+        return CollectAsStateDetector()
+    }
+
+    override fun getIssues(): List<Issue> {
+        return listOf(
+            CollectAsStateDetector.ISSUE,
+        )
+    }
+
+    @Test
+    fun testViolation() {
+        lint()
+            .files(COLLECT_AS_STATE_STUB, COLLECT_WITH_LIFECYCLE_AS_STATE_STUB, GOOD_FILE, BAD_FILE)
+            .issues(CollectAsStateDetector.ISSUE)
+            .run()
+            .expect(
+                """
+src/com/android/internal/systemui/lint/Bad.kt:3: Error: collectAsState considered harmful [OverlyEagerCollectAsState]
+import androidx.compose.runtime.collectAsState
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+1 errors, 0 warnings
+                """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testNoViolation() {
+        lint()
+            .files(COLLECT_AS_STATE_STUB, COLLECT_WITH_LIFECYCLE_AS_STATE_STUB, GOOD_FILE)
+            .issues(CollectAsStateDetector.ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    companion object {
+        private val COLLECT_AS_STATE_STUB =
+            TestFiles.kotlin(
+                """
+                package androidx.compose.runtime
+
+                fun collectAsState() {}
+            """
+                    .trimIndent()
+            )
+        private val COLLECT_WITH_LIFECYCLE_AS_STATE_STUB =
+            TestFiles.kotlin(
+                """
+                package androidx.lifecycle.compose
+
+                fun collectAsStateWithLifecycle() {}
+            """
+                    .trimIndent()
+            )
+
+        private val BAD_FILE =
+            TestFiles.kotlin(
+                """
+                package com.android.internal.systemui.lint
+
+                import androidx.compose.runtime.collectAsState
+
+                class Bad
+            """
+                    .trimIndent()
+            )
+
+        private val GOOD_FILE =
+            TestFiles.kotlin(
+                """
+                package com.android.internal.systemui.lint
+
+                import androidx.lifecycle.compose.collectAsStateWithLifecycle
+
+                class Good
+            """
+                    .trimIndent()
+            )
+    }
+}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SingletonAndroidComponentDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SingletonAndroidComponentDetectorTest.kt
new file mode 100644
index 0000000..0606af8
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SingletonAndroidComponentDetectorTest.kt
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class SingletonAndroidComponentDetectorTest : SystemUILintDetectorTest() {
+    override fun getDetector(): Detector = SingletonAndroidComponentDetector()
+
+    override fun getIssues(): List<Issue> = listOf(SingletonAndroidComponentDetector.ISSUE)
+
+    @Test
+    fun testBindsServiceAsSingleton() {
+        lint()
+            .files(
+                TestFiles.kotlin(
+                    """
+                    package test.pkg
+
+                    import android.app.Service
+                    import com.android.systemui.dagger.SysUISingleton
+                    import dagger.Binds
+                    import dagger.Module
+                    import dagger.multibindings.ClassKey
+                    import dagger.multibindings.IntoMap
+
+                    @Module
+                    interface BadModule {
+                       @SysUISingleton
+                       @Binds
+                       @IntoMap
+                       @ClassKey(SingletonService::class)
+                       fun bindSingletonService(service: SingletonService): Service
+                    }
+                """
+                        .trimIndent()
+                ),
+                *stubs
+            )
+            .issues(SingletonAndroidComponentDetector.ISSUE)
+            .run()
+            .expect(
+                """
+                src/test/pkg/BadModule.kt:12: Error: Do not bind Activities, Services, or BroadcastReceivers as Singleton. [SingletonAndroidComponent]
+                   @SysUISingleton
+                   ~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testProvidesBroadcastReceiverAsSingleton() {
+        lint()
+            .files(
+                TestFiles.kotlin(
+                    """
+                    package test.pkg
+
+                    import android.content.BroadcastReceiver
+                    import com.android.systemui.dagger.SysUISingleton
+                    import dagger.Provides
+                    import dagger.Module
+                    import dagger.multibindings.ClassKey
+                    import dagger.multibindings.IntoMap
+
+                    @Module
+                    abstract class BadModule {
+                       @SysUISingleton
+                       @Provides
+                       @IntoMap
+                       @ClassKey(SingletonBroadcastReceiver::class)
+                       fun providesSingletonBroadcastReceiver(br: SingletonBroadcastReceiver): BroadcastReceiver {
+                          return br
+                       }
+                    }
+                """
+                        .trimIndent()
+                ),
+                *stubs
+            )
+            .issues(SingletonAndroidComponentDetector.ISSUE)
+            .run()
+            .expect(
+                """
+                src/test/pkg/BadModule.kt:12: Error: Do not bind Activities, Services, or BroadcastReceivers as Singleton. [SingletonAndroidComponent]
+                   @SysUISingleton
+                   ~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """
+                    .trimIndent()
+            )
+    }
+    @Test
+    fun testMarksActivityAsSingleton() {
+        lint()
+            .files(
+                TestFiles.kotlin(
+                    """
+                    package test.pkg
+
+                    import android.app.Activity
+                    import com.android.systemui.dagger.SysUISingleton
+
+                    @SysUISingleton
+                    class BadActivity : Activity() {
+                    }
+                """
+                        .trimIndent()
+                ),
+                *stubs
+            )
+            .issues(SingletonAndroidComponentDetector.ISSUE)
+            .run()
+            .expect(
+                """
+                src/test/pkg/BadActivity.kt:6: Error: Do not mark Activities or Services as Singleton. [SingletonAndroidComponent]
+                @SysUISingleton
+                ~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """
+                    .trimIndent()
+            )
+    }
+    @Test
+    fun testMarksBroadcastReceiverAsSingleton() {
+        lint()
+            .files(
+                TestFiles.kotlin(
+                    """
+                    package test.pkg
+
+                    import android.content.BroadcastReceiver
+                    import com.android.systemui.dagger.SysUISingleton
+
+                    @SysUISingleton
+                    class SingletonReceveiver : BroadcastReceiver() {
+                    }
+                """
+                        .trimIndent()
+                ),
+                *stubs
+            )
+            .issues(SingletonAndroidComponentDetector.ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    // Define stubs for Android imports. The tests don't run on Android so
+    // they don't "see" any of Android specific classes. We need to define
+    // the method parameters for proper resolution.
+    private val singletonStub: TestFile =
+        java(
+            """
+        package com.android.systemui.dagger;
+
+        public @interface SysUISingleton {
+        }
+        """
+        )
+
+    private val stubs = arrayOf(singletonStub) + androidStubs
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
index c7f0a96..17a6061 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
@@ -134,13 +134,15 @@
     override val expandable: Expandable =
         object : Expandable {
             override fun activityTransitionController(
-                cujType: Int?,
+                launchCujType: Int?,
+                cookie: ActivityTransitionAnimator.TransitionCookie?,
+                returnCujType: Int?
             ): ActivityTransitionAnimator.Controller? {
                 if (!isComposed.value) {
                     return null
                 }
 
-                return activityController(cujType)
+                return activityController(launchCujType, cookie, returnCujType)
             }
 
             override fun dialogTransitionController(
@@ -262,10 +264,27 @@
     }
 
     /** Create an [ActivityTransitionAnimator.Controller] that can be used to animate activities. */
-    private fun activityController(cujType: Int?): ActivityTransitionAnimator.Controller {
+    private fun activityController(
+        launchCujType: Int?,
+        cookie: ActivityTransitionAnimator.TransitionCookie?,
+        returnCujType: Int?
+    ): ActivityTransitionAnimator.Controller {
         val delegate = transitionController()
         return object :
             ActivityTransitionAnimator.Controller, TransitionAnimator.Controller by delegate {
+            /**
+             * CUJ identifier accounting for whether this controller is for a launch or a return.
+             */
+            private val cujType: Int?
+                get() =
+                    if (isLaunching) {
+                        launchCujType
+                    } else {
+                        returnCujType
+                    }
+
+            override val transitionCookie = cookie
+
             override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
                 delegate.onTransitionAnimationStart(isExpandingFullyAbove)
                 overlay.value = composeViewRoot.rootView.overlay as ViewGroupOverlay
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index c22b50d..fa01a4b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -56,7 +56,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -77,6 +76,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import androidx.compose.ui.unit.times
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.PlatformButton
 import com.android.compose.animation.Easings
 import com.android.compose.animation.scene.ElementKey
@@ -111,7 +111,7 @@
     dialogFactory: BouncerDialogFactory,
     modifier: Modifier = Modifier,
 ) {
-    val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsState()
+    val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsStateWithLifecycle()
     val layout = calculateLayout(isSideBySideSupported = isSideBySideSupported)
 
     Box(
@@ -220,7 +220,7 @@
     viewModel: BouncerViewModel,
     modifier: Modifier = Modifier,
 ) {
-    val authMethod by viewModel.authMethodViewModel.collectAsState()
+    val authMethod by viewModel.authMethodViewModel.collectAsStateWithLifecycle()
 
     Row(
         modifier =
@@ -316,7 +316,7 @@
     val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) }
     val isHeightExpanded =
         LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded
-    val authMethod by viewModel.authMethodViewModel.collectAsState()
+    val authMethod by viewModel.authMethodViewModel.collectAsStateWithLifecycle()
 
     Row(
         modifier =
@@ -480,7 +480,7 @@
     modifier: Modifier = Modifier,
 ) {
     val foldPosture: FoldPosture by foldPosture()
-    val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsState()
+    val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsStateWithLifecycle()
     val isSplitAroundTheFold = foldPosture == FoldPosture.Tabletop && isSplitAroundTheFoldRequired
     val currentSceneKey =
         if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey
@@ -562,7 +562,7 @@
     viewModel: BouncerMessageViewModel,
     modifier: Modifier = Modifier,
 ) {
-    val message: MessageViewModel? by viewModel.message.collectAsState()
+    val message: MessageViewModel? by viewModel.message.collectAsStateWithLifecycle()
 
     DisposableEffect(Unit) {
         viewModel.onShown()
@@ -612,7 +612,7 @@
     modifier: Modifier = Modifier,
 ) {
     val authMethodViewModel: AuthMethodBouncerViewModel? by
-        viewModel.authMethodViewModel.collectAsState()
+        viewModel.authMethodViewModel.collectAsStateWithLifecycle()
 
     when (val nonNullViewModel = authMethodViewModel) {
         is PinBouncerViewModel ->
@@ -642,7 +642,7 @@
     modifier: Modifier = Modifier,
 ) {
     val authMethodViewModel: AuthMethodBouncerViewModel? by
-        viewModel.authMethodViewModel.collectAsState()
+        viewModel.authMethodViewModel.collectAsStateWithLifecycle()
 
     when (val nonNullViewModel = authMethodViewModel) {
         is PinBouncerViewModel -> {
@@ -668,7 +668,8 @@
     viewModel: BouncerViewModel,
     modifier: Modifier = Modifier,
 ) {
-    val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState()
+    val actionButton: BouncerActionButtonModel? by
+        viewModel.actionButton.collectAsStateWithLifecycle()
     val appearFadeInAnimatable = remember { Animatable(0f) }
     val appearMoveAnimatable = remember { Animatable(0f) }
     val appearAnimationInitialOffset = with(LocalDensity.current) { 80.dp.toPx() }
@@ -735,7 +736,7 @@
     bouncerViewModel: BouncerViewModel,
     dialogFactory: BouncerDialogFactory,
 ) {
-    val dialogViewModel by bouncerViewModel.dialogViewModel.collectAsState()
+    val dialogViewModel by bouncerViewModel.dialogViewModel.collectAsStateWithLifecycle()
     var dialog: AlertDialog? by remember { mutableStateOf(null) }
 
     dialogViewModel?.let { viewModel ->
@@ -772,8 +773,8 @@
         return
     }
 
-    val selectedUserImage by viewModel.selectedUserImage.collectAsState(null)
-    val dropdownItems by viewModel.userSwitcherDropdown.collectAsState(emptyList())
+    val selectedUserImage by viewModel.selectedUserImage.collectAsStateWithLifecycle(null)
+    val dropdownItems by viewModel.userSwitcherDropdown.collectAsStateWithLifecycle(emptyList())
 
     Column(
         horizontalAlignment = Alignment.CenterHorizontally,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index 2dcd0ff..203bd7a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -27,7 +27,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.ui.ExperimentalComposeUiApi
@@ -49,6 +48,7 @@
 import androidx.compose.ui.text.input.PasswordVisualTransformation
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.PlatformIconButton
 import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
 import com.android.systemui.common.ui.compose.SelectedUserAwareInputConnection
@@ -62,18 +62,20 @@
     modifier: Modifier = Modifier,
 ) {
     val focusRequester = remember { FocusRequester() }
-    val isTextFieldFocusRequested by viewModel.isTextFieldFocusRequested.collectAsState()
+    val isTextFieldFocusRequested by
+        viewModel.isTextFieldFocusRequested.collectAsStateWithLifecycle()
     LaunchedEffect(isTextFieldFocusRequested) {
         if (isTextFieldFocusRequested) {
             focusRequester.requestFocus()
         }
     }
 
-    val password: String by viewModel.password.collectAsState()
-    val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
-    val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
-    val isImeSwitcherButtonVisible by viewModel.isImeSwitcherButtonVisible.collectAsState()
-    val selectedUserId by viewModel.selectedUserId.collectAsState()
+    val password: String by viewModel.password.collectAsStateWithLifecycle()
+    val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsStateWithLifecycle()
+    val animateFailure: Boolean by viewModel.animateFailure.collectAsStateWithLifecycle()
+    val isImeSwitcherButtonVisible by
+        viewModel.isImeSwitcherButtonVisible.collectAsStateWithLifecycle()
+    val selectedUserId by viewModel.selectedUserId.collectAsStateWithLifecycle()
 
     DisposableEffect(Unit) { onDispose { viewModel.onHidden() } }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index d7e9c10..9c2fd64 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -30,7 +30,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -47,6 +46,7 @@
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.res.integerResource
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.Easings
 import com.android.compose.modifiers.thenIf
 import com.android.internal.R
@@ -86,14 +86,15 @@
     val lineStrokeWidth = with(density) { LINE_STROKE_WIDTH_DP.dp.toPx() }
 
     // All dots that should be rendered on the grid.
-    val dots: List<PatternDotViewModel> by viewModel.dots.collectAsState()
+    val dots: List<PatternDotViewModel> by viewModel.dots.collectAsStateWithLifecycle()
     // The most recently selected dot, if the user is currently dragging.
-    val currentDot: PatternDotViewModel? by viewModel.currentDot.collectAsState()
+    val currentDot: PatternDotViewModel? by viewModel.currentDot.collectAsStateWithLifecycle()
     // The dots selected so far, if the user is currently dragging.
-    val selectedDots: List<PatternDotViewModel> by viewModel.selectedDots.collectAsState()
-    val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
-    val isAnimationEnabled: Boolean by viewModel.isPatternVisible.collectAsState()
-    val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
+    val selectedDots: List<PatternDotViewModel> by
+        viewModel.selectedDots.collectAsStateWithLifecycle()
+    val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsStateWithLifecycle()
+    val isAnimationEnabled: Boolean by viewModel.isPatternVisible.collectAsStateWithLifecycle()
+    val animateFailure: Boolean by viewModel.animateFailure.collectAsStateWithLifecycle()
 
     // Map of animatables for the scale of each dot, keyed by dot.
     val dotScalingAnimatables = remember(dots) { dots.associateWith { Animatable(1f) } }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 5651a46..64ace2f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -33,7 +33,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -49,6 +48,7 @@
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.Easings
 import com.android.compose.grid.VerticalGrid
 import com.android.compose.modifiers.thenIf
@@ -74,12 +74,13 @@
 ) {
     DisposableEffect(Unit) { onDispose { viewModel.onHidden() } }
 
-    val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
-    val backspaceButtonAppearance by viewModel.backspaceButtonAppearance.collectAsState()
-    val confirmButtonAppearance by viewModel.confirmButtonAppearance.collectAsState()
-    val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
+    val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsStateWithLifecycle()
+    val backspaceButtonAppearance by
+        viewModel.backspaceButtonAppearance.collectAsStateWithLifecycle()
+    val confirmButtonAppearance by viewModel.confirmButtonAppearance.collectAsStateWithLifecycle()
+    val animateFailure: Boolean by viewModel.animateFailure.collectAsStateWithLifecycle()
     val isDigitButtonAnimationEnabled: Boolean by
-        viewModel.isDigitButtonAnimationEnabled.collectAsState()
+        viewModel.isDigitButtonAnimationEnabled.collectAsStateWithLifecycle()
 
     val buttonScaleAnimatables = remember { List(12) { Animatable(1f) } }
     LaunchedEffect(animateFailure) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
index 1a97912..465eade 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
@@ -42,7 +42,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.key
 import androidx.compose.runtime.mutableStateListOf
@@ -65,6 +64,7 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.Dialog
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.PlatformOutlinedButton
 import com.android.compose.animation.Easings
 import com.android.keyguard.PinShapeAdapter
@@ -86,7 +86,7 @@
     viewModel: PinBouncerViewModel,
     modifier: Modifier = Modifier,
 ) {
-    val hintedPinLength: Int? by viewModel.hintedPinLength.collectAsState()
+    val hintedPinLength: Int? by viewModel.hintedPinLength.collectAsStateWithLifecycle()
     val shapeAnimations = rememberShapeAnimations(viewModel.pinShapes)
 
     // The display comes in two different flavors:
@@ -119,7 +119,7 @@
     hintedPinLength: Int,
     modifier: Modifier = Modifier,
 ) {
-    val pinInput: PinInputViewModel by viewModel.pinInput.collectAsState()
+    val pinInput: PinInputViewModel by viewModel.pinInput.collectAsStateWithLifecycle()
     // [ClearAll] marker pointing at the beginning of the current pin input.
     // When a new [ClearAll] token is added to the [pinInput], the clear-all animation is played
     // and the marker is advanced manually to the most recent marker. See LaunchedEffect below.
@@ -257,9 +257,10 @@
 
 @Composable
 private fun SimArea(viewModel: PinBouncerViewModel) {
-    val isLockedEsim by viewModel.isLockedEsim.collectAsState()
-    val isSimUnlockingDialogVisible by viewModel.isSimUnlockingDialogVisible.collectAsState()
-    val errorDialogMessage by viewModel.errorDialogMessage.collectAsState()
+    val isLockedEsim by viewModel.isLockedEsim.collectAsStateWithLifecycle()
+    val isSimUnlockingDialogVisible by
+        viewModel.isSimUnlockingDialogVisible.collectAsStateWithLifecycle()
+    val errorDialogMessage by viewModel.errorDialogMessage.collectAsStateWithLifecycle()
     var unlockDialog: Dialog? by remember { mutableStateOf(null) }
     var errorDialog: Dialog? by remember { mutableStateOf(null) }
     val context = LocalView.current.context
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt
index c8e1450..694326d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt
@@ -35,7 +35,7 @@
  * ```
  * @Composable
  * fun YourFunction(viewModel: YourViewModel) {
- *     val selectedUserId by viewModel.selectedUserId.collectAsState()
+ *     val selectedUserId by viewModel.selectedUserId.collectAsStateWithLifecycle()
  *
  *     SelectedUserAwareInputConnection(selectedUserId) {
  *         TextField(...)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutout.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutout.kt
index 3eb1b14..604b517 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutout.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutout.kt
@@ -35,6 +35,7 @@
     val viewDisplayCutoutKeyguardStatusBarView: ViewDisplayCutout? = null,
 ) {
     fun width() = abs(right.value - left.value).dp
+    fun height() = abs(bottom.value - top.value).dp
 }
 
 enum class CutoutLocation {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
index 8144d15..296fc27 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
@@ -22,12 +22,12 @@
 import androidx.compose.foundation.layout.systemBars
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.staticCompositionLocalOf
 import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import kotlinx.coroutines.flow.StateFlow
 
 /** The bounds and [CutoutLocation] of the current display. */
@@ -45,7 +45,7 @@
     screenCornerRadius: Float,
     content: @Composable () -> Unit,
 ) {
-    val cutout by displayCutout.collectAsState()
+    val cutout by displayCutout.collectAsStateWithLifecycle()
     val screenCornerRadiusDp = with(LocalDensity.current) { screenCornerRadius.toDp() }
 
     val density = LocalDensity.current
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index ec3c003..feb1f5b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -2,19 +2,28 @@
 
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.FixedSizeEdgeDetector
 import com.android.compose.animation.scene.LowestZIndexScenePicker
 import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
@@ -25,6 +34,7 @@
 import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.observableTransitionState
 import com.android.compose.animation.scene.transitions
+import com.android.systemui.Flags
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalTransitionKeys
 import com.android.systemui.communal.ui.compose.extensions.allowGestures
@@ -33,33 +43,38 @@
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
 import com.android.systemui.scene.ui.composable.SceneTransitionLayoutDataSource
-import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 
 object Communal {
     object Elements {
         val Scrim = ElementKey("Scrim", scenePicker = LowestZIndexScenePicker)
-        val Content = ElementKey("CommunalContent")
+        val Grid = ElementKey("CommunalContent")
+        val LockIcon = ElementKey("CommunalLockIcon")
+        val IndicationArea = ElementKey("CommunalIndicationArea")
     }
 }
 
+object AllElements : ElementMatcher {
+    override fun matches(key: ElementKey, scene: SceneKey) = true
+}
+
 val sceneTransitions = transitions {
     to(CommunalScenes.Communal, key = CommunalTransitionKeys.SimpleFade) {
         spec = tween(durationMillis = 250)
-        fade(Communal.Elements.Scrim)
-        fade(Communal.Elements.Content)
+        fade(AllElements)
     }
     to(CommunalScenes.Communal) {
         spec = tween(durationMillis = 1000)
-        translate(Communal.Elements.Content, Edge.Right)
-        timestampRange(startMillis = 167, endMillis = 334) {
-            fade(Communal.Elements.Scrim)
-            fade(Communal.Elements.Content)
-        }
+        translate(Communal.Elements.Grid, Edge.Right)
+        timestampRange(startMillis = 167, endMillis = 334) { fade(AllElements) }
     }
     to(CommunalScenes.Blank) {
         spec = tween(durationMillis = 1000)
-        translate(Communal.Elements.Content, Edge.Right)
-        timestampRange(endMillis = 167) { fade(Communal.Elements.Content) }
+        translate(Communal.Elements.Grid, Edge.Right)
+        timestampRange(endMillis = 167) {
+            fade(Communal.Elements.Grid)
+            fade(Communal.Elements.IndicationArea)
+            fade(Communal.Elements.LockIcon)
+        }
         timestampRange(startMillis = 167, endMillis = 334) { fade(Communal.Elements.Scrim) }
     }
 }
@@ -75,12 +90,15 @@
     modifier: Modifier = Modifier,
     viewModel: CommunalViewModel,
     dataSourceDelegator: SceneDataSourceDelegator,
-    dialogFactory: SystemUIDialogFactory,
     colors: CommunalColors,
+    content: CommunalContent,
 ) {
     val coroutineScope = rememberCoroutineScope()
-    val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank)
-    val touchesAllowed by viewModel.touchesAllowed.collectAsState(initial = false)
+    val currentSceneKey: SceneKey by
+        viewModel.currentScene.collectAsStateWithLifecycle(CommunalScenes.Blank)
+    val touchesAllowed by viewModel.touchesAllowed.collectAsStateWithLifecycle(initialValue = false)
+    val showGestureIndicator by
+        viewModel.showGestureIndicator.collectAsStateWithLifecycle(initialValue = false)
     val state: MutableSceneTransitionLayoutState = remember {
         MutableSceneTransitionLayoutState(
             initialScene = currentSceneKey,
@@ -119,7 +137,19 @@
                 )
         ) {
             // This scene shows nothing only allowing for transitions to the communal scene.
-            Box(modifier = Modifier.fillMaxSize())
+            // TODO(b/339667383): remove this temporary swipe gesture handle
+            Row(modifier = Modifier.fillMaxSize(), horizontalArrangement = Arrangement.End) {
+                if (showGestureIndicator && Flags.glanceableHubGestureHandle()) {
+                    Box(
+                        modifier =
+                            Modifier.height(220.dp)
+                                .width(4.dp)
+                                .align(Alignment.CenterVertically)
+                                .background(color = Color.White, RoundedCornerShape(4.dp))
+                    )
+                    Spacer(modifier = Modifier.width(12.dp))
+                }
+            }
         }
 
         scene(
@@ -127,7 +157,7 @@
             userActions =
                 mapOf(Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank)
         ) {
-            CommunalScene(viewModel, colors, dialogFactory, modifier = modifier)
+            CommunalScene(colors, content)
         }
     }
 
@@ -139,12 +169,11 @@
 /** Scene containing the glanceable hub UI. */
 @Composable
 private fun SceneScope.CommunalScene(
-    viewModel: CommunalViewModel,
     colors: CommunalColors,
-    dialogFactory: SystemUIDialogFactory,
+    content: CommunalContent,
     modifier: Modifier = Modifier,
 ) {
-    val backgroundColor by colors.backgroundColor.collectAsState()
+    val backgroundColor by colors.backgroundColor.collectAsStateWithLifecycle()
 
     Box(
         modifier =
@@ -152,7 +181,5 @@
                 .fillMaxSize()
                 .background(Color(backgroundColor.toArgb())),
     )
-    Box(modifier.element(Communal.Elements.Content)) {
-        CommunalHub(viewModel = viewModel, dialogFactory = dialogFactory)
-    }
+    with(content) { Content(modifier = modifier) }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
new file mode 100644
index 0000000..77665155
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.communal.ui.compose
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.unit.IntRect
+import com.android.compose.animation.scene.SceneScope
+import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
+import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import javax.inject.Inject
+
+/** Renders the content of the glanceable hub. */
+class CommunalContent
+@Inject
+constructor(
+    private val viewModel: CommunalViewModel,
+    private val dialogFactory: SystemUIDialogFactory,
+    private val lockSection: LockSection,
+) {
+
+    @Composable
+    fun SceneScope.Content(modifier: Modifier = Modifier) {
+        Layout(
+            modifier = modifier.fillMaxSize(),
+            content = {
+                CommunalHub(
+                    viewModel = viewModel,
+                    dialogFactory = dialogFactory,
+                    modifier = Modifier.element(Communal.Elements.Grid)
+                )
+                with(lockSection) {
+                    LockIcon(
+                        overrideColor = LocalAndroidColorScheme.current.onPrimaryContainer,
+                        modifier = Modifier.element(Communal.Elements.LockIcon)
+                    )
+                }
+            }
+        ) { measurables, constraints ->
+            val communalGridMeasurable = measurables[0]
+            val lockIconMeasurable = measurables[1]
+
+            val noMinConstraints =
+                constraints.copy(
+                    minWidth = 0,
+                    minHeight = 0,
+                )
+
+            val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+            val lockIconBounds =
+                IntRect(
+                    left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+                    top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+                    right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+                    bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+                )
+
+            val communalGridPlaceable =
+                communalGridMeasurable.measure(
+                    noMinConstraints.copy(maxHeight = lockIconBounds.top)
+                )
+
+            layout(constraints.maxWidth, constraints.maxHeight) {
+                communalGridPlaceable.place(
+                    x = 0,
+                    y = 0,
+                )
+                lockIconPlaceable.place(
+                    x = lockIconBounds.left,
+                    y = lockIconBounds.top,
+                )
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 79b57ca7..cd27d57 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -48,6 +48,7 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.wrapContentHeight
@@ -59,7 +60,8 @@
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.icons.outlined.Delete
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material.icons.filled.Close
 import androidx.compose.material.icons.outlined.Edit
 import androidx.compose.material.icons.outlined.TouchApp
 import androidx.compose.material.icons.outlined.Widgets
@@ -77,7 +79,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.State
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -87,8 +88,6 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.draw.scale
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ColorFilter
@@ -107,7 +106,6 @@
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.semantics.CustomAccessibilityAction
 import androidx.compose.ui.semantics.contentDescription
@@ -121,9 +119,11 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.unit.times
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.compose.ui.window.Popup
-import androidx.core.view.setPadding
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.window.layout.WindowMetricsCalculator
 import com.android.compose.modifiers.thenIf
 import com.android.compose.theme.LocalAndroidColorScheme
@@ -156,20 +156,21 @@
     onOpenWidgetPicker: (() -> Unit)? = null,
     onEditDone: (() -> Unit)? = null,
 ) {
-    val communalContent by viewModel.communalContent.collectAsState(initial = emptyList())
-    val currentPopup by viewModel.currentPopup.collectAsState(initial = null)
+    val communalContent by
+        viewModel.communalContent.collectAsStateWithLifecycle(initialValue = emptyList())
+    val currentPopup by viewModel.currentPopup.collectAsStateWithLifecycle(initialValue = null)
     var removeButtonCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
     var toolbarSize: IntSize? by remember { mutableStateOf(null) }
     var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
     var isDraggingToRemove by remember { mutableStateOf(false) }
     val gridState = rememberLazyGridState()
     val contentListState = rememberContentListState(widgetConfigurator, communalContent, viewModel)
-    val reorderingWidgets by viewModel.reorderingWidgets.collectAsState()
-    val selectedKey = viewModel.selectedKey.collectAsState()
+    val reorderingWidgets by viewModel.reorderingWidgets.collectAsStateWithLifecycle()
+    val selectedKey = viewModel.selectedKey.collectAsStateWithLifecycle()
     val removeButtonEnabled by remember {
         derivedStateOf { selectedKey.value != null || reorderingWidgets }
     }
-    val isEmptyState by viewModel.isEmptyState.collectAsState(initial = false)
+    val isEmptyState by viewModel.isEmptyState.collectAsStateWithLifecycle(initialValue = false)
 
     val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
     val contentOffset = beforeContentPadding(contentPadding).toOffset()
@@ -264,20 +265,8 @@
             }
         }
 
-        // TODO(b/326060686): Remove this once keyguard indication area can persist over hub
-        if (viewModel is CommunalViewModel) {
-            val isUnlocked by viewModel.deviceUnlocked.collectAsState(initial = false)
-            LockStateIcon(
-                modifier =
-                    Modifier.align(Alignment.BottomCenter)
-                        .padding(bottom = Dimensions.LockIconBottomPadding),
-                isUnlocked = isUnlocked,
-            )
-        }
-
         if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
             Toolbar(
-                isDraggingToRemove = isDraggingToRemove,
                 setToolbarSize = { toolbarSize = it },
                 setRemoveButtonCoordinates = { removeButtonCoordinates = it },
                 onEditDone = onEditDone,
@@ -315,9 +304,9 @@
 
         if (viewModel is CommunalViewModel && dialogFactory != null) {
             val isEnableWidgetDialogShowing by
-                viewModel.isEnableWidgetDialogShowing.collectAsState(false)
+                viewModel.isEnableWidgetDialogShowing.collectAsStateWithLifecycle(false)
             val isEnableWorkProfileDialogShowing by
-                viewModel.isEnableWorkProfileDialogShowing.collectAsState(false)
+                viewModel.isEnableWorkProfileDialogShowing.collectAsStateWithLifecycle(false)
 
             EnableWidgetDialog(
                 isEnableWidgetDialogVisible = isEnableWidgetDialogShowing,
@@ -438,8 +427,8 @@
         state = gridState,
         rows = GridCells.Fixed(CommunalContentSize.FULL.span),
         contentPadding = contentPadding,
-        horizontalArrangement = Arrangement.spacedBy(32.dp),
-        verticalArrangement = Arrangement.spacedBy(32.dp),
+        horizontalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing),
+        verticalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing),
     ) {
         items(
             count = list.size,
@@ -452,7 +441,7 @@
                     Dimensions.CardWidth.value,
                     list[index].size.dp().value,
                 )
-            val cardModifier = Modifier.size(width = size.width.dp, height = size.height.dp)
+            val cardModifier = Modifier.requiredSize(width = size.width.dp, height = size.height.dp)
             if (viewModel.isEditMode && dragDropState != null) {
                 val selected by
                     remember(index) { derivedStateOf { list[index].key == selectedKey.value } }
@@ -549,26 +538,6 @@
     }
 }
 
-@Composable
-private fun LockStateIcon(
-    isUnlocked: Boolean,
-    modifier: Modifier = Modifier,
-) {
-    val colors = LocalAndroidColorScheme.current
-    val resource =
-        if (isUnlocked) {
-            R.drawable.ic_unlocked
-        } else {
-            R.drawable.ic_lock
-        }
-    Icon(
-        painter = painterResource(id = resource),
-        contentDescription = null,
-        tint = colors.onPrimaryContainer,
-        modifier = modifier.size(Dimensions.LockIconSize),
-    )
-}
-
 /**
  * Toolbar that contains action buttons to
  * 1) open the widget picker
@@ -577,7 +546,6 @@
  */
 @Composable
 private fun Toolbar(
-    isDraggingToRemove: Boolean,
     removeEnabled: Boolean,
     onRemoveClicked: () -> Unit,
     setToolbarSize: (toolbarSize: IntSize) -> Unit,
@@ -591,7 +559,7 @@
             label = "RemoveButtonAlphaAnimation"
         )
 
-    Row(
+    Box(
         modifier =
             Modifier.fillMaxWidth()
                 .padding(
@@ -600,65 +568,54 @@
                     end = Dimensions.ToolbarPaddingHorizontal,
                 )
                 .onSizeChanged { setToolbarSize(it) },
-        horizontalArrangement = Arrangement.SpaceBetween,
-        verticalAlignment = Alignment.CenterVertically
     ) {
         val spacerModifier = Modifier.width(ButtonDefaults.IconSpacing)
-        Button(
-            onClick = onOpenWidgetPicker,
-            colors = filledButtonColors(),
-            contentPadding = Dimensions.ButtonPadding
-        ) {
-            Icon(Icons.Default.Add, stringResource(R.string.hub_mode_add_widget_button_text))
-            Spacer(spacerModifier)
-            Text(
-                text = stringResource(R.string.hub_mode_add_widget_button_text),
-            )
+
+        if (!removeEnabled) {
+            Button(
+                modifier = Modifier.align(Alignment.CenterStart),
+                onClick = onOpenWidgetPicker,
+                colors = filledButtonColors(),
+                contentPadding = Dimensions.ButtonPadding
+            ) {
+                Icon(Icons.Default.Add, stringResource(R.string.hub_mode_add_widget_button_text))
+                Spacer(spacerModifier)
+                Text(
+                    text = stringResource(R.string.hub_mode_add_widget_button_text),
+                )
+            }
         }
 
-        val colors = LocalAndroidColorScheme.current
-        if (isDraggingToRemove) {
+        if (removeEnabled) {
             Button(
-                // Button is disabled to make it non-clickable
-                enabled = false,
-                onClick = {},
-                colors =
-                    ButtonDefaults.buttonColors(
-                        disabledContainerColor = colors.primary,
-                        disabledContentColor = colors.onPrimary,
-                    ),
-                contentPadding = Dimensions.ButtonPadding,
-                modifier = Modifier.onGloballyPositioned { setRemoveButtonCoordinates(it) }
-            ) {
-                RemoveButtonContent(spacerModifier)
-            }
-        } else {
-            OutlinedButton(
-                enabled = removeEnabled,
                 onClick = onRemoveClicked,
-                colors =
-                    ButtonDefaults.outlinedButtonColors(
-                        contentColor = colors.primary,
-                        disabledContentColor = colors.primary
-                    ),
-                border = BorderStroke(width = 1.0.dp, color = colors.primary),
+                colors = filledButtonColors(),
                 contentPadding = Dimensions.ButtonPadding,
                 modifier =
                     Modifier.graphicsLayer { alpha = removeButtonAlpha }
                         .onGloballyPositioned { setRemoveButtonCoordinates(it) }
+                        .align(Alignment.Center)
             ) {
                 RemoveButtonContent(spacerModifier)
             }
         }
 
-        Button(
-            onClick = onEditDone,
-            colors = filledButtonColors(),
-            contentPadding = Dimensions.ButtonPadding
-        ) {
-            Text(
-                text = stringResource(R.string.hub_mode_editing_exit_button_text),
-            )
+        if (!removeEnabled) {
+            Button(
+                modifier = Modifier.align(Alignment.CenterEnd),
+                onClick = onEditDone,
+                colors = filledButtonColors(),
+                contentPadding = Dimensions.ButtonPadding
+            ) {
+                Icon(
+                    Icons.Default.Check,
+                    stringResource(id = R.string.hub_mode_editing_exit_button_text)
+                )
+                Spacer(spacerModifier)
+                Text(
+                    text = stringResource(R.string.hub_mode_editing_exit_button_text),
+                )
+            }
         }
     }
 }
@@ -762,7 +719,7 @@
 
 @Composable
 private fun RemoveButtonContent(spacerModifier: Modifier) {
-    Icon(Icons.Outlined.Delete, stringResource(R.string.button_to_remove_widget))
+    Icon(Icons.Default.Close, stringResource(R.string.button_to_remove_widget))
     Spacer(spacerModifier)
     Text(
         text = stringResource(R.string.button_to_remove_widget),
@@ -804,6 +761,8 @@
         is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(modifier)
         is CommunalContentModel.WidgetContent.DisabledWidget ->
             DisabledWidgetPlaceholder(model, viewModel, modifier)
+        is CommunalContentModel.WidgetContent.PendingWidget ->
+            PendingWidgetPlaceholder(model, modifier)
         is CommunalContentModel.CtaTileInViewMode -> CtaTileInViewModeContent(viewModel, modifier)
         is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier)
         is CommunalContentModel.Tutorial -> TutorialContent(modifier)
@@ -836,12 +795,10 @@
                 containerColor = colors.primary,
                 contentColor = colors.onPrimary,
             ),
-        shape = RoundedCornerShape(80.dp, 40.dp, 80.dp, 40.dp)
+        shape = RoundedCornerShape(68.dp, 34.dp, 68.dp, 34.dp)
     ) {
         Column(
-            modifier = Modifier.fillMaxSize().padding(horizontal = 82.dp),
-            verticalArrangement =
-                Arrangement.spacedBy(Dimensions.Spacing, Alignment.CenterVertically),
+            modifier = Modifier.fillMaxSize().padding(vertical = 38.dp, horizontal = 70.dp),
             horizontalAlignment = Alignment.CenterHorizontally,
         ) {
             Icon(
@@ -849,11 +806,13 @@
                 contentDescription = stringResource(R.string.cta_label_to_open_widget_picker),
                 modifier = Modifier.size(Dimensions.IconSize),
             )
+            Spacer(modifier = Modifier.size(6.dp))
             Text(
                 text = stringResource(R.string.cta_label_to_edit_widget),
-                style = MaterialTheme.typography.titleLarge,
+                style = MaterialTheme.typography.titleMedium,
                 textAlign = TextAlign.Center,
             )
+            Spacer(modifier = Modifier.size(20.dp))
             Row(
                 modifier = Modifier.fillMaxWidth(),
                 horizontalArrangement = Arrangement.Center,
@@ -869,9 +828,10 @@
                 ) {
                     Text(
                         text = stringResource(R.string.cta_tile_button_to_dismiss),
+                        fontSize = 12.sp,
                     )
                 }
-                Spacer(modifier = Modifier.size(Dimensions.Spacing))
+                Spacer(modifier = Modifier.size(14.dp))
                 Button(
                     colors =
                         ButtonDefaults.buttonColors(
@@ -883,6 +843,7 @@
                 ) {
                     Text(
                         text = stringResource(R.string.cta_tile_button_to_open_widget_editor),
+                        fontSize = 12.sp,
                     )
                 }
             }
@@ -902,7 +863,7 @@
     contentListState: ContentListState,
 ) {
     val context = LocalContext.current
-    val isFocusable by viewModel.isFocusable.collectAsState(initial = false)
+    val isFocusable by viewModel.isFocusable.collectAsStateWithLifecycle(initialValue = false)
     val accessibilityLabel =
         remember(model, context) {
             model.providerInfo.loadLabel(context.packageManager).toString().trim()
@@ -910,7 +871,7 @@
     val clickActionLabel = stringResource(R.string.accessibility_action_label_select_widget)
     val removeWidgetActionLabel = stringResource(R.string.accessibility_action_label_remove_widget)
     val placeWidgetActionLabel = stringResource(R.string.accessibility_action_label_place_widget)
-    val selectedKey by viewModel.selectedKey.collectAsState()
+    val selectedKey by viewModel.selectedKey.collectAsStateWithLifecycle()
     val selectedIndex =
         selectedKey?.let { key -> contentListState.list.indexOfFirst { it.key == key } }
     Box(
@@ -929,36 +890,36 @@
                     Modifier.semantics {
                         contentDescription = accessibilityLabel
                         onClick(label = clickActionLabel, action = null)
-                            val deleteAction =
-                                CustomAccessibilityAction(removeWidgetActionLabel) {
-                                    contentListState.onRemove(index)
-                                    contentListState.onSaveList()
-                                    true
-                                }
-                            val selectWidgetAction =
-                                CustomAccessibilityAction(clickActionLabel) {
-                                    val currentWidgetKey =
-                                        index?.let {
-                                            keyAtIndexIfEditable(contentListState.list, index)
-                                        }
-                                    viewModel.setSelectedKey(currentWidgetKey)
-                                    true
-                                }
-
-                            val actions = mutableListOf(deleteAction, selectWidgetAction)
-
-                            if (selectedIndex != null && selectedIndex != index) {
-                                actions.add(
-                                    CustomAccessibilityAction(placeWidgetActionLabel) {
-                                        contentListState.onMove(selectedIndex!!, index)
-                                        contentListState.onSaveList()
-                                        viewModel.setSelectedKey(null)
-                                        true
+                        val deleteAction =
+                            CustomAccessibilityAction(removeWidgetActionLabel) {
+                                contentListState.onRemove(index)
+                                contentListState.onSaveList()
+                                true
+                            }
+                        val selectWidgetAction =
+                            CustomAccessibilityAction(clickActionLabel) {
+                                val currentWidgetKey =
+                                    index?.let {
+                                        keyAtIndexIfEditable(contentListState.list, index)
                                     }
-                                )
+                                viewModel.setSelectedKey(currentWidgetKey)
+                                true
                             }
 
-                            customActions = actions
+                        val actions = mutableListOf(deleteAction, selectWidgetAction)
+
+                        if (selectedIndex != null && selectedIndex != index) {
+                            actions.add(
+                                CustomAccessibilityAction(placeWidgetActionLabel) {
+                                    contentListState.onMove(selectedIndex!!, index)
+                                    contentListState.onSaveList()
+                                    viewModel.setSelectedKey(null)
+                                    true
+                                }
+                            )
+                        }
+
+                        customActions = actions
                     }
                 }
     ) {
@@ -968,10 +929,14 @@
                 model.appWidgetHost
                     .createViewForCommunal(context, model.appWidgetId, model.providerInfo)
                     .apply {
-                        updateAppWidgetSize(Bundle.EMPTY, listOf(size))
-                        // Remove the extra padding applied to AppWidgetHostView to allow widgets to
-                        // occupy the entire box.
-                        setPadding(0)
+                        updateAppWidgetSize(
+                            /* newOptions = */ Bundle(),
+                            /* minWidth = */ size.width.toInt(),
+                            /* minHeight = */ size.height.toInt(),
+                            /* maxWidth = */ size.width.toInt(),
+                            /* maxHeight = */ size.height.toInt(),
+                            /* ignorePadding = */ true
+                        )
                         accessibilityDelegate = viewModel.widgetAccessibilityDelegate
                     }
             },
@@ -1074,13 +1039,43 @@
         Image(
             painter = rememberDrawablePainter(icon.loadDrawable(context)),
             contentDescription = stringResource(R.string.icon_description_for_disabled_widget),
-            modifier = Modifier.size(48.dp),
+            modifier = Modifier.size(Dimensions.IconSize),
             colorFilter = ColorFilter.colorMatrix(Colors.DisabledColorFilter),
         )
     }
 }
 
 @Composable
+fun PendingWidgetPlaceholder(
+    model: CommunalContentModel.WidgetContent.PendingWidget,
+    modifier: Modifier = Modifier,
+) {
+    val context = LocalContext.current
+    val icon: Icon =
+        if (model.icon != null) {
+            Icon.createWithBitmap(model.icon)
+        } else {
+            Icon.createWithResource(context, android.R.drawable.sym_def_app_icon)
+        }
+
+    Column(
+        modifier =
+            modifier.background(
+                MaterialTheme.colorScheme.surfaceVariant,
+                RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
+            ),
+        verticalArrangement = Arrangement.Center,
+        horizontalAlignment = Alignment.CenterHorizontally,
+    ) {
+        Image(
+            painter = rememberDrawablePainter(icon.loadDrawable(context)),
+            contentDescription = stringResource(R.string.icon_description_for_pending_widget),
+            modifier = Modifier.size(Dimensions.IconSize),
+        )
+    }
+}
+
+@Composable
 private fun SmartspaceContent(
     model: CommunalContentModel.Smartspace,
     modifier: Modifier = Modifier,
@@ -1121,7 +1116,7 @@
 @Composable
 fun AccessibilityContainer(viewModel: BaseCommunalViewModel, content: @Composable () -> Unit) {
     val context = LocalContext.current
-    val isFocusable by viewModel.isFocusable.collectAsState(initial = false)
+    val isFocusable by viewModel.isFocusable.collectAsStateWithLifecycle(initialValue = false)
     Box(
         modifier =
             Modifier.fillMaxWidth().wrapContentHeight().thenIf(
@@ -1164,7 +1159,11 @@
 @Composable
 private fun gridContentPadding(isEditMode: Boolean, toolbarSize: IntSize?): PaddingValues {
     if (!isEditMode || toolbarSize == null) {
-        return PaddingValues(start = 48.dp, end = 48.dp, top = Dimensions.GridTopSpacing)
+        return PaddingValues(
+            start = Dimensions.ItemSpacing,
+            end = Dimensions.ItemSpacing,
+            top = Dimensions.GridTopSpacing,
+        )
     }
     val context = LocalContext.current
     val density = LocalDensity.current
@@ -1227,18 +1226,19 @@
 }
 
 object Dimensions {
-    val CardWidth = 424.dp
-    val CardHeightFull = 596.dp
-    val CardHeightHalf = 282.dp
-    val CardHeightThird = 177.33.dp
-    val CardOutlineWidth = 3.dp
-    val GridTopSpacing = 64.dp
+    val CardHeightFull = 530.dp
+    val GridTopSpacing = 114.dp
     val GridHeight = CardHeightFull + GridTopSpacing
-    val Spacing = 16.dp
+    val ItemSpacing = 50.dp
+    val CardHeightHalf = (CardHeightFull - ItemSpacing) / 2
+    val CardHeightThird = (CardHeightFull - (2 * ItemSpacing)) / 3
+    val CardWidth = 360.dp
+    val CardOutlineWidth = 3.dp
+    val Spacing = ItemSpacing / 2
 
     // The sizing/padding of the toolbar in glanceable hub edit mode
     val ToolbarPaddingTop = 27.dp
-    val ToolbarPaddingHorizontal = 16.dp
+    val ToolbarPaddingHorizontal = ItemSpacing
     val ToolbarButtonPaddingHorizontal = 24.dp
     val ToolbarButtonPaddingVertical = 16.dp
     val ButtonPadding =
@@ -1246,10 +1246,7 @@
             vertical = ToolbarButtonPaddingVertical,
             horizontal = ToolbarButtonPaddingHorizontal,
         )
-    val IconSize = 48.dp
-
-    val LockIconSize = 52.dp
-    val LockIconBottomPadding = 70.dp
+    val IconSize = 40.dp
 }
 
 private object Colors {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt
index e77ade9..17dac7e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt
@@ -18,11 +18,11 @@
 
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.produceState
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.window.layout.WindowInfoTracker
 import com.android.systemui.fold.ui.helper.FoldPosture
 import com.android.systemui.fold.ui.helper.foldPostureInternal
@@ -32,7 +32,8 @@
 fun foldPosture(): State<FoldPosture> {
     val context = LocalContext.current
     val infoTracker = remember(context) { WindowInfoTracker.getOrCreate(context) }
-    val layoutInfo by infoTracker.windowLayoutInfo(context).collectAsState(initial = null)
+    val layoutInfo by
+        infoTracker.windowLayoutInfo(context).collectAsStateWithLifecycle(initialValue = null)
 
     return produceState<FoldPosture>(
         initialValue = FoldPosture.Folded,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
index a8d801a..67840c7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
@@ -29,7 +29,6 @@
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.key
 import androidx.compose.ui.Alignment
@@ -37,6 +36,7 @@
 import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.theme.PlatformTheme
 import com.android.systemui.keyboard.stickykeys.shared.model.Locked
 import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
@@ -57,7 +57,7 @@
 
 @Composable
 fun StickyKeysIndicator(viewModel: StickyKeysIndicatorViewModel) {
-    val stickyKeys by viewModel.indicatorContent.collectAsState(emptyMap())
+    val stickyKeys by viewModel.indicatorContent.collectAsStateWithLifecycle(emptyMap())
     StickyKeysIndicator(stickyKeys)
 }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
index 4bef9ef..ca4ff83 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -18,12 +18,13 @@
 
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalView
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
@@ -51,7 +52,7 @@
         modifier: Modifier = Modifier,
     ) {
         val coroutineScope = rememberCoroutineScope()
-        val blueprintId by viewModel.blueprintId(coroutineScope).collectAsState()
+        val blueprintId by viewModel.blueprintId(coroutineScope).collectAsStateWithLifecycle()
         val view = LocalView.current
         DisposableEffect(view) {
             clockInteractor.clockEventController.registerListeners(view)
@@ -60,6 +61,6 @@
         }
 
         val blueprint = blueprintByBlueprintId[blueprintId] ?: return
-        with(blueprint) { Content(modifier) }
+        with(blueprint) { Content(modifier.sysuiResTag("keyguard_root_view")) }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
index 472484a..4555f13 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
@@ -26,13 +26,13 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.input.pointer.pointerInput
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
 
 /** Container for lockscreen content that handles long-press to bring up the settings menu. */
@@ -42,7 +42,8 @@
     modifier: Modifier = Modifier,
     content: @Composable BoxScope.(onSettingsMenuPlaces: (coordinates: Rect?) -> Unit) -> Unit,
 ) {
-    val isEnabled: Boolean by viewModel.isLongPressHandlingEnabled.collectAsState(initial = false)
+    val isEnabled: Boolean by
+        viewModel.isLongPressHandlingEnabled.collectAsStateWithLifecycle(initialValue = false)
     val (settingsMenuBounds, setSettingsMenuBounds) = remember { mutableStateOf<Rect?>(null) }
     val interactionSource = remember { MutableInteractionSource() }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
index 8129e41..ba25719 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
@@ -21,11 +21,11 @@
 import androidx.compose.foundation.layout.systemBars
 import androidx.compose.foundation.layout.union
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalDensity
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.plugins.clocks.ClockController
@@ -37,7 +37,7 @@
 fun rememberBurnIn(
     clockInteractor: KeyguardClockInteractor,
 ): BurnInState {
-    val clock by clockInteractor.currentClock.collectAsState()
+    val clock by clockInteractor.currentClock.collectAsStateWithLifecycle()
 
     val (smartspaceTop, onSmartspaceTopChanged) = remember { mutableStateOf<Float?>(null) }
     val (smallClockTop, onSmallClockTopChanged) = remember { mutableStateOf<Float?>(null) }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 3152535..a39fa64 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -22,15 +22,16 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.unit.IntRect
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.padding
+import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -67,8 +68,8 @@
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
         val shouldUseSplitNotificationShade by
-            viewModel.shouldUseSplitNotificationShade.collectAsState()
-        val unfoldTranslations by viewModel.unfoldTranslations.collectAsState()
+            viewModel.shouldUseSplitNotificationShade.collectAsStateWithLifecycle()
+        val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle()
 
         LockscreenLongPress(
             viewModel = viewModel.longPress,
@@ -129,7 +130,7 @@
                     with(lockSection) { LockIcon() }
 
                     // Aligned to bottom and constrained to below the lock icon.
-                    Column(modifier = Modifier.fillMaxWidth()) {
+                    Column(modifier = Modifier.fillMaxWidth().sysuiResTag("keyguard_bottom_area")) {
                         if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
                             with(ambientIndicationSectionOptional.get()) {
                                 AmbientIndication(modifier = Modifier.fillMaxWidth())
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index 9d31955..c83f62c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -22,13 +22,13 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.unit.IntRect
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.padding
 import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
@@ -70,8 +70,8 @@
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
         val shouldUseSplitNotificationShade by
-            viewModel.shouldUseSplitNotificationShade.collectAsState()
-        val unfoldTranslations by viewModel.unfoldTranslations.collectAsState()
+            viewModel.shouldUseSplitNotificationShade.collectAsStateWithLifecycle()
+        val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle()
 
         LockscreenLongPress(
             viewModel = viewModel.longPress,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
index c109e51..aaf49ff 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard.ui.composable.modifier
 
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -25,6 +24,7 @@
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.layout.boundsInWindow
 import androidx.compose.ui.layout.onPlaced
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.keyguard.ui.viewmodel.BurnInScaleViewModel
@@ -44,8 +44,10 @@
     val translationYState = remember { mutableStateOf(0F) }
     val copiedParams = params.copy(translationY = { translationYState.value })
     val burnIn = viewModel.movement(copiedParams)
-    val translationX by burnIn.map { it.translationX.toFloat() }.collectAsState(initial = 0f)
-    val translationY by burnIn.map { it.translationY.toFloat() }.collectAsState(initial = 0f)
+    val translationX by
+        burnIn.map { it.translationX.toFloat() }.collectAsStateWithLifecycle(initialValue = 0f)
+    val translationY by
+        burnIn.map { it.translationY.toFloat() }.collectAsStateWithLifecycle(initialValue = 0f)
     translationYState.value = translationY
     val scaleViewModel by
         burnIn
@@ -55,18 +57,14 @@
                     scaleClockOnly = it.scaleClockOnly,
                 )
             }
-            .collectAsState(initial = BurnInScaleViewModel())
+            .collectAsStateWithLifecycle(initialValue = BurnInScaleViewModel())
 
     return this.graphicsLayer {
-        val scale =
-            when {
-                scaleViewModel.scaleClockOnly && isClock -> scaleViewModel.scale
-                else -> 1f
-            }
-
         this.translationX = if (isClock) 0F else translationX
         this.translationY = translationY
         this.alpha = alpha
+
+        val scale = if (scaleViewModel.scaleClockOnly) scaleViewModel.scale else 1f
         this.scaleX = scale
         this.scaleY = scale
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
index 09ec76d..218779d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
@@ -25,13 +25,13 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.core.view.contains
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.padding
 import com.android.systemui.customization.R
@@ -59,9 +59,11 @@
         onTopChanged: (top: Float?) -> Unit,
         modifier: Modifier = Modifier,
     ) {
-        val currentClock by viewModel.currentClock.collectAsState()
+        val currentClock by viewModel.currentClock.collectAsStateWithLifecycle()
         val smallTopMargin by
-            viewModel.smallClockTopMargin.collectAsState(viewModel.getSmallClockTopMargin())
+            viewModel.smallClockTopMargin.collectAsStateWithLifecycle(
+                viewModel.getSmallClockTopMargin()
+            )
         if (currentClock?.smallClock?.view == null) {
             return
         }
@@ -89,7 +91,7 @@
 
     @Composable
     fun SceneScope.LargeClock(burnInParams: BurnInParameters, modifier: Modifier = Modifier) {
-        val currentClock by viewModel.currentClock.collectAsState()
+        val currentClock by viewModel.currentClock.collectAsStateWithLifecycle()
         if (currentClock?.largeClock?.view == null) {
             return
         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
index 9f02201..4129c25 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
@@ -22,6 +22,7 @@
 import android.view.WindowManager
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.layout
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.unit.Constraints
@@ -68,7 +69,7 @@
     private val notificationPanelView: NotificationPanelView,
 ) {
     @Composable
-    fun SceneScope.LockIcon(modifier: Modifier = Modifier) {
+    fun SceneScope.LockIcon(overrideColor: Color? = null, modifier: Modifier = Modifier) {
         if (!KeyguardBottomAreaRefactor.isEnabled && !DeviceEntryUdfpsRefactor.isEnabled) {
             return
         }
@@ -93,6 +94,7 @@
                                 deviceEntryBackgroundViewModel.get(),
                                 falsingManager.get(),
                                 vibratorHelper.get(),
+                                overrideColor,
                             )
                         }
                     } else {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt
index 556bbbe..3ca2b9c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt
@@ -18,9 +18,11 @@
 
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.SceneScope
-import com.android.systemui.keyguard.ui.viewmodel.MediaCarouselViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardMediaViewModel
 import com.android.systemui.media.controls.ui.composable.MediaCarousel
 import com.android.systemui.media.controls.ui.controller.MediaCarouselController
 import com.android.systemui.media.controls.ui.view.MediaHost
@@ -33,20 +35,15 @@
 constructor(
     private val mediaCarouselController: MediaCarouselController,
     @param:Named(MediaModule.KEYGUARD) private val mediaHost: MediaHost,
-    private val mediaCarouselViewModel: MediaCarouselViewModel,
+    private val keyguardMediaViewModel: KeyguardMediaViewModel,
 ) {
 
-    private fun isVisible(): Boolean {
-        if (mediaCarouselController.mediaFrame == null) {
-            return false
-        }
-        return mediaCarouselViewModel.isMediaVisible
-    }
-
     @Composable
     fun SceneScope.KeyguardMediaCarousel() {
+        val isMediaVisible by keyguardMediaViewModel.isMediaVisible.collectAsStateWithLifecycle()
+
         MediaCarousel(
-            isVisible = ::isVisible,
+            isVisible = isMediaVisible,
             mediaHost = mediaHost,
             modifier = Modifier.fillMaxWidth(),
             carouselController = mediaCarouselController,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index f48fa88..7f80dfa 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -20,13 +20,13 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.thenIf
 import com.android.systemui.Flags
@@ -40,16 +40,19 @@
 import com.android.systemui.res.R
 import com.android.systemui.shade.LargeScreenHeaderHelper
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
+import dagger.Lazy
 import javax.inject.Inject
 
 @SysUISingleton
 class NotificationSection
 @Inject
 constructor(
+    private val stackScrollView: Lazy<NotificationScrollView>,
     private val viewModel: NotificationsPlaceholderViewModel,
     private val aodBurnInViewModel: AodBurnInViewModel,
     sharedNotificationContainer: SharedNotificationContainer,
@@ -88,9 +91,9 @@
     @Composable
     fun SceneScope.Notifications(burnInParams: BurnInParameters?, modifier: Modifier = Modifier) {
         val shouldUseSplitNotificationShade by
-            lockscreenContentViewModel.shouldUseSplitNotificationShade.collectAsState()
+            lockscreenContentViewModel.shouldUseSplitNotificationShade.collectAsStateWithLifecycle()
         val areNotificationsVisible by
-            lockscreenContentViewModel.areNotificationsVisible.collectAsState()
+            lockscreenContentViewModel.areNotificationsVisible.collectAsStateWithLifecycle()
         val splitShadeTopMargin: Dp =
             if (Flags.centralizedStatusBarHeightFix()) {
                 LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp
@@ -103,6 +106,7 @@
         }
 
         ConstrainedNotificationStack(
+            stackScrollView = stackScrollView.get(),
             viewModel = viewModel,
             modifier =
                 modifier
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
index fc8b3b9..44bda95 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
@@ -26,7 +26,6 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.width
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -34,6 +33,7 @@
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.padding
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
@@ -160,7 +160,7 @@
     private fun Weather(
         modifier: Modifier = Modifier,
     ) {
-        val isVisible by keyguardSmartspaceViewModel.isWeatherVisible.collectAsState()
+        val isVisible by keyguardSmartspaceViewModel.isWeatherVisible.collectAsStateWithLifecycle()
         if (!isVisible) {
             return
         }
@@ -187,7 +187,7 @@
     private fun Date(
         modifier: Modifier = Modifier,
     ) {
-        val isVisible by keyguardSmartspaceViewModel.isDateVisible.collectAsState()
+        val isVisible by keyguardSmartspaceViewModel.isDateVisible.collectAsStateWithLifecycle()
         if (!isVisible) {
             return
         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
index 63c70c9..0673153 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -25,7 +25,6 @@
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
@@ -33,10 +32,10 @@
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntOffset
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.modifiers.thenIf
-import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene
 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.smallClockScene
@@ -62,9 +61,9 @@
     fun DefaultClockLayout(
         modifier: Modifier = Modifier,
     ) {
-        val currentClockLayout by clockViewModel.currentClockLayout.collectAsState()
+        val currentClockLayout by clockViewModel.currentClockLayout.collectAsStateWithLifecycle()
         val hasCustomPositionUpdatedAnimation by
-            clockViewModel.hasCustomPositionUpdatedAnimation.collectAsState()
+            clockViewModel.hasCustomPositionUpdatedAnimation.collectAsStateWithLifecycle()
         val currentScene =
             when (currentClockLayout) {
                 KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_LARGE_CLOCK ->
@@ -80,7 +79,7 @@
             }
 
         SceneTransitionLayout(
-            modifier = modifier.sysuiResTag("keyguard_clock_container"),
+            modifier = modifier,
             currentScene = currentScene,
             onChangeScene = {},
             transitions = ClockTransition.defaultClockTransitions,
@@ -133,7 +132,7 @@
     @Composable
     private fun SceneScope.LargeClockWithSmartSpace(shouldOffSetClockToOneHalf: Boolean = false) {
         val burnIn = rememberBurnIn(clockInteractor)
-        val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsState()
+        val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsStateWithLifecycle()
 
         LaunchedEffect(isLargeClockVisible) {
             if (isLargeClockVisible) {
@@ -170,8 +169,8 @@
     @Composable
     private fun SceneScope.WeatherLargeClockWithSmartSpace(modifier: Modifier = Modifier) {
         val burnIn = rememberBurnIn(clockInteractor)
-        val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsState()
-        val currentClockState = clockViewModel.currentClock.collectAsState()
+        val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsStateWithLifecycle()
+        val currentClockState = clockViewModel.currentClock.collectAsStateWithLifecycle()
 
         LaunchedEffect(isLargeClockVisible) {
             if (isLargeClockVisible) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
index 241c171..581f3a5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
@@ -42,12 +42,12 @@
 
 @Composable
 fun SceneScope.MediaCarousel(
-    isVisible: () -> Boolean,
+    isVisible: Boolean,
     mediaHost: MediaHost,
     modifier: Modifier = Modifier,
     carouselController: MediaCarouselController,
 ) {
-    if (!isVisible()) {
+    if (!isVisible) {
         return
     }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
index 2ba78cf..fdf82ca 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
@@ -30,11 +30,15 @@
  */
 fun NotificationScrimNestedScrollConnection(
     scrimOffset: () -> Float,
-    onScrimOffsetChanged: (Float) -> Unit,
+    snapScrimOffset: (Float) -> Unit,
+    animateScrimOffset: (Float) -> Unit,
     minScrimOffset: () -> Float,
     maxScrimOffset: Float,
     contentHeight: () -> Float,
     minVisibleScrimHeight: () -> Float,
+    isCurrentGestureOverscroll: () -> Boolean,
+    onStart: (Float) -> Unit = {},
+    onStop: (Float) -> Unit = {},
 ): PriorityNestedScrollConnection {
     return PriorityNestedScrollConnection(
         orientation = Orientation.Vertical,
@@ -49,7 +53,7 @@
         // scrolling down and content is done scrolling to top. After that, the scrim
         // needs to collapse; collapse the scrim until it is at the maxScrimOffset.
         canStartPostScroll = { offsetAvailable, _ ->
-            offsetAvailable > 0 && scrimOffset() < maxScrimOffset
+            offsetAvailable > 0 && (scrimOffset() < maxScrimOffset || isCurrentGestureOverscroll())
         },
         canStartPostFling = { false },
         canContinueScroll = {
@@ -57,7 +61,7 @@
             minScrimOffset() < currentHeight && currentHeight < maxScrimOffset
         },
         canScrollOnFling = true,
-        onStart = { /* do nothing */},
+        onStart = { offsetAvailable -> onStart(offsetAvailable) },
         onScroll = { offsetAvailable ->
             val currentHeight = scrimOffset()
             val amountConsumed =
@@ -68,10 +72,16 @@
                     val amountLeft = minScrimOffset() - currentHeight
                     offsetAvailable.coerceAtLeast(amountLeft)
                 }
-            onScrimOffsetChanged(currentHeight + amountConsumed)
+            snapScrimOffset(currentHeight + amountConsumed)
             amountConsumed
         },
         // Don't consume the velocity on pre/post fling
-        onStop = { 0f },
+        onStop = { velocityAvailable ->
+            onStop(velocityAvailable)
+            if (scrimOffset() < minScrimOffset()) {
+                animateScrimOffset(minScrimOffset())
+            }
+            0f
+        },
     )
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackContentHeight.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackContentHeight.kt
new file mode 100644
index 0000000..9f829cc
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackContentHeight.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.notifications.ui.composable
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.node.LayoutModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.invalidateMeasurement
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntOffset
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
+
+/**
+ * Modify element, which updates the height to be the same as the Notification stack height returned
+ * by the legacy Notification stack scroll view in [NotificationScrollView.intrinsicStackHeight].
+ *
+ * @param view Notification stack scroll view
+ * @param padding extra padding in pixels to be added to the received content height.
+ */
+fun Modifier.notificationStackHeight(view: NotificationScrollView, padding: Int = 0) =
+    this then StackLayoutElement(view, padding)
+
+private data class StackLayoutElement(
+    val view: NotificationScrollView,
+    val padding: Int,
+) : ModifierNodeElement<StackLayoutNode>() {
+
+    override fun create(): StackLayoutNode = StackLayoutNode(view, padding)
+
+    override fun update(node: StackLayoutNode) {
+        check(view == node.view) { "Trying to reuse the node with a new View." }
+        if (node.padding != padding) {
+            node.padding = padding
+            node.invalidateMeasureIfAttached()
+        }
+    }
+}
+
+private class StackLayoutNode(val view: NotificationScrollView, var padding: Int) :
+    LayoutModifierNode, Modifier.Node() {
+
+    private val stackHeightChangedListener = Runnable { invalidateMeasureIfAttached() }
+
+    override fun onAttach() {
+        super.onAttach()
+        view.addStackHeightChangedListener(stackHeightChangedListener)
+    }
+
+    override fun onDetach() {
+        super.onDetach()
+        view.removeStackHeightChangedListener(stackHeightChangedListener)
+    }
+
+    override fun MeasureScope.measure(
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult {
+        val contentHeight = padding + view.intrinsicStackHeight
+        val placeable =
+            measurable.measure(
+                constraints.copy(minHeight = contentHeight, maxHeight = contentHeight)
+            )
+
+        return layout(placeable.width, placeable.height) { placeable.place(IntOffset.Zero) }
+    }
+
+    override fun toString(): String {
+        return "StackLayoutNode(view=$view padding:$padding)"
+    }
+
+    fun invalidateMeasureIfAttached() {
+        if (isAttached) {
+            this.invalidateMeasurement()
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 6e987bd..cf2e895 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -18,6 +18,8 @@
 package com.android.systemui.notifications.ui.composable
 
 import android.util.Log
+import androidx.compose.animation.core.Animatable
+import androidx.compose.foundation.ScrollState
 import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.scrollBy
 import androidx.compose.foundation.layout.Box
@@ -29,7 +31,6 @@
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.systemBars
-import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.MaterialTheme
@@ -37,10 +38,10 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.mutableIntStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -62,21 +63,28 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.lerp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.NestedScrollBehavior
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.height
+import com.android.compose.modifiers.thenIf
 import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
 import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
 import com.android.systemui.res.R
+import com.android.systemui.scene.session.ui.composable.SaveableSession
+import com.android.systemui.scene.session.ui.composable.rememberSession
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.shade.ui.composable.ShadeHeader
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_CORNER_RADIUS
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import kotlin.math.roundToInt
+import kotlinx.coroutines.launch
 
 object Notifications {
     object Elements {
@@ -104,7 +112,7 @@
     modifier: Modifier = Modifier,
     isPeekFromBottom: Boolean = false,
 ) {
-    val headsUpHeight = viewModel.headsUpHeight.collectAsState()
+    val headsUpHeight = viewModel.headsUpHeight.collectAsStateWithLifecycle()
 
     Element(
         Notifications.Elements.HeadsUpNotificationPlaceholder,
@@ -130,6 +138,7 @@
 /** Adds the space where notification stack should appear in the scene. */
 @Composable
 fun SceneScope.ConstrainedNotificationStack(
+    stackScrollView: NotificationScrollView,
     viewModel: NotificationsPlaceholderViewModel,
     modifier: Modifier = Modifier,
 ) {
@@ -138,6 +147,7 @@
             modifier.onSizeChanged { viewModel.onConstrainedAvailableSpaceChanged(it.height) }
     ) {
         NotificationPlaceholder(
+            stackScrollView = stackScrollView,
             viewModel = viewModel,
             modifier = Modifier.fillMaxSize(),
         )
@@ -154,25 +164,40 @@
  */
 @Composable
 fun SceneScope.NotificationScrollingStack(
+    shadeSession: SaveableSession,
+    stackScrollView: NotificationScrollView,
     viewModel: NotificationsPlaceholderViewModel,
     maxScrimTop: () -> Float,
     shouldPunchHoleBehindScrim: Boolean,
+    shadeMode: ShadeMode,
     modifier: Modifier = Modifier,
 ) {
+    val coroutineScope = rememberCoroutineScope()
     val density = LocalDensity.current
     val screenCornerRadius = LocalScreenCornerRadius.current
     val scrimCornerRadius = dimensionResource(R.dimen.notification_scrim_corner_radius)
-    val scrollState = rememberScrollState()
-    val syntheticScroll = viewModel.syntheticScroll.collectAsState(0f)
-    val expansionFraction by viewModel.expandFraction.collectAsState(0f)
+    val scrollState =
+        shadeSession.rememberSaveableSession(saver = ScrollState.Saver, key = null) {
+            ScrollState(initial = 0)
+        }
+    val syntheticScroll = viewModel.syntheticScroll.collectAsStateWithLifecycle(0f)
+    val isCurrentGestureOverscroll =
+        viewModel.isCurrentGestureOverscroll.collectAsStateWithLifecycle(false)
+    val expansionFraction by viewModel.expandFraction.collectAsStateWithLifecycle(0f)
 
     val navBarHeight =
         with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() }
     val screenHeight = LocalRawScreenHeight.current
 
-    val stackHeight = viewModel.stackHeight.collectAsState()
+    /**
+     * The height in px of the contents of notification stack. Depending on the number of
+     * notifications, this can exceed the space available on screen to show notifications, at which
+     * point the notification stack should become scrollable.
+     */
+    val stackHeight = remember { mutableIntStateOf(0) }
 
-    val scrimRounding = viewModel.shadeScrimRounding.collectAsState(ShadeScrimRounding())
+    val scrimRounding =
+        viewModel.shadeScrimRounding.collectAsStateWithLifecycle(ShadeScrimRounding())
 
     // the offset for the notifications scrim. Its upper bound is 0, and its lower bound is
     // calculated in minScrimOffset. The scrim is the same height as the screen minus the
@@ -180,7 +205,7 @@
     // When fully expanded (scrimOffset = minScrimOffset), its top bound is at minScrimStartY,
     // which is equal to the height of the Shade Header. Thus, when the scrim is fully expanded, the
     // entire height of the scrim is visible on screen.
-    val scrimOffset = remember { mutableStateOf(0f) }
+    val scrimOffset = shadeSession.rememberSession { Animatable(0f) }
 
     // set the bounds to null when the scrim disappears
     DisposableEffect(Unit) { onDispose { viewModel.onScrimBoundsChanged(null) } }
@@ -203,8 +228,8 @@
     // if contentHeight drops below minimum visible scrim height while scrim is
     // expanded, reset scrim offset.
     LaunchedEffect(stackHeight, scrimOffset) {
-        snapshotFlow { stackHeight.value < minVisibleScrimHeight() && scrimOffset.value < 0f }
-            .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.value = 0f }
+        snapshotFlow { stackHeight.intValue < minVisibleScrimHeight() && scrimOffset.value < 0f }
+            .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.snapTo(0f) }
     }
 
     // if we receive scroll delta from NSSL, offset the scrim and placeholder accordingly.
@@ -214,7 +239,7 @@
                 val minOffset = minScrimOffset()
                 if (scrimOffset.value > minOffset) {
                     val remainingDelta = (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f)
-                    scrimOffset.value = (scrimOffset.value - delta).coerceAtLeast(minOffset)
+                    scrimOffset.snapTo((scrimOffset.value - delta).coerceAtLeast(minOffset))
                     if (remainingDelta > 0f) {
                         scrollState.scrollBy(remainingDelta)
                     }
@@ -224,6 +249,27 @@
             }
     }
 
+    val scrimNestedScrollConnection =
+        shadeSession.rememberSession(
+            scrimOffset,
+            maxScrimTop,
+            minScrimTop,
+            isCurrentGestureOverscroll,
+        ) {
+            NotificationScrimNestedScrollConnection(
+                scrimOffset = { scrimOffset.value },
+                snapScrimOffset = { value -> coroutineScope.launch { scrimOffset.snapTo(value) } },
+                animateScrimOffset = { value ->
+                    coroutineScope.launch { scrimOffset.animateTo(value) }
+                },
+                minScrimOffset = minScrimOffset,
+                maxScrimOffset = 0f,
+                contentHeight = { stackHeight.intValue.toFloat() },
+                minVisibleScrimHeight = minVisibleScrimHeight,
+                isCurrentGestureOverscroll = { isCurrentGestureOverscroll.value },
+            )
+        }
+
     Box(
         modifier =
             modifier
@@ -292,30 +338,23 @@
                     .debugBackground(viewModel, DEBUG_BOX_COLOR)
         ) {
             NotificationPlaceholder(
+                stackScrollView = stackScrollView,
                 viewModel = viewModel,
                 modifier =
                     Modifier.verticalNestedScrollToScene(
                             topBehavior = NestedScrollBehavior.EdgeWithPreview,
+                            isExternalOverscrollGesture = { isCurrentGestureOverscroll.value }
                         )
-                        .nestedScroll(
-                            remember(
-                                scrimOffset,
-                                maxScrimTop,
-                                minScrimTop,
-                            ) {
-                                NotificationScrimNestedScrollConnection(
-                                    scrimOffset = { scrimOffset.value },
-                                    onScrimOffsetChanged = { scrimOffset.value = it },
-                                    minScrimOffset = minScrimOffset,
-                                    maxScrimOffset = 0f,
-                                    contentHeight = { stackHeight.value },
-                                    minVisibleScrimHeight = minVisibleScrimHeight,
-                                )
-                            }
-                        )
+                        .thenIf(shadeMode == ShadeMode.Single) {
+                            Modifier.nestedScroll(scrimNestedScrollConnection)
+                        }
                         .verticalScroll(scrollState)
                         .fillMaxWidth()
-                        .height { (stackHeight.value + navBarHeight).roundToInt() },
+                        .notificationStackHeight(
+                            view = stackScrollView,
+                            padding = navBarHeight.toInt()
+                        )
+                        .onSizeChanged { size -> stackHeight.intValue = size.height },
             )
         }
         HeadsUpNotificationSpace(viewModel = viewModel)
@@ -356,6 +395,7 @@
 
 @Composable
 private fun SceneScope.NotificationPlaceholder(
+    stackScrollView: NotificationScrollView,
     viewModel: NotificationsPlaceholderViewModel,
     modifier: Modifier = Modifier,
 ) {
@@ -374,10 +414,8 @@
                             " bounds=${coordinates.boundsInWindow()}"
                     }
                     // NOTE: positionInWindow.y scrolls off screen, but boundsInWindow.top will not
-                    viewModel.onStackBoundsChanged(
-                        top = positionInWindow.y,
-                        bottom = positionInWindow.y + coordinates.size.height,
-                    )
+                    stackScrollView.setStackTop(positionInWindow.y)
+                    stackScrollView.setStackBottom(positionInWindow.y + coordinates.size.height)
                 }
     ) {
         content {}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeSessionModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeSessionModule.kt
new file mode 100644
index 0000000..076c917
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeSessionModule.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.notifications.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.saveable.Saver
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.session.shared.SessionStorage
+import com.android.systemui.scene.session.ui.composable.SaveableSession
+import com.android.systemui.scene.session.ui.composable.Session
+import dagger.Module
+import dagger.Provides
+
+@Module
+object NotificationsShadeSessionModule {
+    @Provides @SysUISingleton fun provideShadeSessionStorage(): SessionStorage = SessionStorage()
+
+    @Provides
+    fun provideShadeSession(storage: SessionStorage): SaveableSession =
+        object : SaveableSession, Session by Session(storage) {
+            @Composable
+            override fun <T : Any> rememberSaveableSession(
+                vararg inputs: Any?,
+                saver: Saver<T, out Any>,
+                key: String?,
+                init: () -> T
+            ): T = rememberSession(key, inputs = inputs, init = init)
+        }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
index 73cb72c..b808044 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
@@ -36,7 +36,6 @@
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.key
 import androidx.compose.ui.Alignment
@@ -46,6 +45,7 @@
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
@@ -64,8 +64,8 @@
     viewModel: PeopleViewModel,
     onResult: (PeopleViewModel.Result) -> Unit,
 ) {
-    val priorityTiles by viewModel.priorityTiles.collectAsState()
-    val recentTiles by viewModel.recentTiles.collectAsState()
+    val priorityTiles by viewModel.priorityTiles.collectAsStateWithLifecycle()
+    val recentTiles by viewModel.recentTiles.collectAsStateWithLifecycle()
 
     // Call [onResult] this activity when the ViewModel tells us so.
     LaunchedEffect(viewModel.result) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 5f84dd4..e8da4bd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -44,7 +44,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -69,6 +68,7 @@
 import androidx.compose.ui.unit.sp
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.compose.animation.Expandable
 import com.android.compose.animation.scene.SceneScope
@@ -132,8 +132,8 @@
     val context = LocalContext.current
 
     // Collect alphas as soon as we are composed, even when not visible.
-    val alpha by viewModel.alpha.collectAsState()
-    val backgroundAlpha = viewModel.backgroundAlpha.collectAsState()
+    val alpha by viewModel.alpha.collectAsStateWithLifecycle()
+    val backgroundAlpha = viewModel.backgroundAlpha.collectAsStateWithLifecycle()
 
     var security by remember { mutableStateOf<FooterActionsSecurityButtonViewModel?>(null) }
     var foregroundServices by remember {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt
index ca6b343..73a624a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt
@@ -21,13 +21,13 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.offset
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.modifiers.height
 import com.android.compose.modifiers.width
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
@@ -40,13 +40,13 @@
     qsSceneAdapter: QSSceneAdapter,
     modifier: Modifier = Modifier,
 ) {
-    val isShowing by viewModel.isShowing.collectAsState()
+    val isShowing by viewModel.isShowing.collectAsStateWithLifecycle()
     val mirrorAlpha by
         animateFloatAsState(
             targetValue = if (isShowing) 1f else 0f,
             label = "alphaAnimationBrightnessMirrorShowing",
         )
-    val mirrorOffsetAndSize by viewModel.locationAndSize.collectAsState()
+    val mirrorOffsetAndSize by viewModel.locationAndSize.collectAsStateWithLifecycle()
     val offset = IntOffset(0, mirrorOffsetAndSize.yOffset)
 
     Box(modifier = modifier.fillMaxSize().graphicsLayer { alpha = mirrorAlpha }) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index 46be6b8..d109988 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -22,18 +22,19 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.layout
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.MovableElementScenePicker
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.TransitionState
 import com.android.compose.animation.scene.ValueKey
 import com.android.compose.modifiers.thenIf
+import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Companion.Collapsing
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Expanding
@@ -143,7 +144,9 @@
     MovableElement(
         key = QuickSettings.Elements.Content,
         modifier =
-            modifier.fillMaxWidth().layout { measurable, constraints ->
+            modifier.sysuiResTag("quick_settings_panel").fillMaxWidth().layout {
+                measurable,
+                constraints ->
                 val placeable = measurable.measure(constraints)
                 // Use the height of the correct view based on the scene it is being composed in
                 val height = heightProvider().coerceAtLeast(0)
@@ -161,9 +164,11 @@
     state: QSSceneAdapter.State,
     modifier: Modifier = Modifier,
 ) {
-    val qsView by qsSceneAdapter.qsView.collectAsState(null)
+    val qsView by qsSceneAdapter.qsView.collectAsStateWithLifecycle(null)
     val isCustomizing by
-        qsSceneAdapter.isCustomizerShowing.collectAsState(qsSceneAdapter.isCustomizerShowing.value)
+        qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle(
+            qsSceneAdapter.isCustomizerShowing.value
+        )
     QuickSettingsTheme {
         val context = LocalContext.current
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 4c0f2e1..d76b19f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -36,6 +36,7 @@
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.displayCutoutPadding
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -50,7 +51,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
@@ -62,12 +62,15 @@
 import androidx.compose.ui.res.colorResource
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.TransitionState
 import com.android.compose.animation.scene.animateSceneFloatAsState
 import com.android.compose.modifiers.thenIf
 import com.android.compose.windowsizeclass.LocalWindowSizeClass
 import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
+import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
 import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dagger.SysUISingleton
@@ -80,16 +83,20 @@
 import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
 import com.android.systemui.res.R
+import com.android.systemui.scene.session.ui.composable.SaveableSession
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
 import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
 import com.android.systemui.shade.ui.composable.Shade
 import com.android.systemui.shade.ui.composable.ShadeHeader
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController
 import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import dagger.Lazy
 import javax.inject.Inject
 import javax.inject.Named
 import kotlin.math.roundToInt
@@ -103,6 +110,8 @@
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
+    private val shadeSession: SaveableSession,
+    private val notificationStackScrollView: Lazy<NotificationScrollView>,
     private val viewModel: QuickSettingsSceneViewModel,
     private val notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
     private val tintedIconManagerFactory: TintedIconManager.Factory,
@@ -125,6 +134,7 @@
         modifier: Modifier,
     ) {
         QuickSettingsScene(
+            notificationStackScrollView = notificationStackScrollView.get(),
             viewModel = viewModel,
             notificationsPlaceholderViewModel = notificationsPlaceholderViewModel,
             createTintedIconManager = tintedIconManagerFactory::create,
@@ -133,12 +143,14 @@
             mediaCarouselController = mediaCarouselController,
             mediaHost = mediaHost,
             modifier = modifier,
+            shadeSession = shadeSession,
         )
     }
 }
 
 @Composable
 private fun SceneScope.QuickSettingsScene(
+    notificationStackScrollView: NotificationScrollView,
     viewModel: QuickSettingsSceneViewModel,
     notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
     createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
@@ -147,8 +159,12 @@
     mediaCarouselController: MediaCarouselController,
     mediaHost: MediaHost,
     modifier: Modifier = Modifier,
+    shadeSession: SaveableSession,
 ) {
-    val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState()
+    val cutoutLocation = LocalDisplayCutout.current.location
+
+    val brightnessMirrorShowing by
+        viewModel.brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle()
     val contentAlpha by
         animateFloatAsState(
             targetValue = if (brightnessMirrorShowing) 0f else 1f,
@@ -179,11 +195,15 @@
                     // scene (and not the one under it) during a scene transition.
                     Modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
                 }
+                .thenIf(cutoutLocation != CutoutLocation.CENTER) {
+                    Modifier.displayCutoutPadding()
+                },
     ) {
-        val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
-        val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsState()
+        val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle()
+        val isCustomizerShowing by
+            viewModel.qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle()
         val customizingAnimationDuration by
-            viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsState()
+            viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsStateWithLifecycle()
         val screenHeight = LocalRawScreenHeight.current
 
         BackHandler(
@@ -276,7 +296,8 @@
                     }
 
                 Column(
-                    modifier = shadeHeaderAndQuickSettingsModifier,
+                    modifier =
+                        shadeHeaderAndQuickSettingsModifier.sysuiResTag("expanded_qs_scroll_view"),
                 ) {
                     when (LocalWindowSizeClass.current.widthSizeClass) {
                         WindowWidthSizeClass.Compact ->
@@ -316,7 +337,6 @@
                                 createTintedIconManager = createTintedIconManager,
                                 createBatteryMeterViewController = createBatteryMeterViewController,
                                 statusBarIconController = statusBarIconController,
-                                modifier = Modifier.padding(horizontal = 16.dp),
                             )
                     }
                     Spacer(modifier = Modifier.height(16.dp))
@@ -325,11 +345,13 @@
                         viewModel.qsSceneAdapter,
                         { viewModel.qsSceneAdapter.qsHeight },
                         isSplitShade = false,
-                        modifier = Modifier.sysuiResTag("expanded_qs_scroll_view")
+                        modifier = Modifier
                     )
 
+                    val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle()
+
                     MediaCarousel(
-                        isVisible = viewModel::isMediaVisible,
+                        isVisible = isMediaVisible,
                         mediaHost = mediaHost,
                         modifier = Modifier.fillMaxWidth(),
                         carouselController = mediaCarouselController,
@@ -342,13 +364,17 @@
                 isCustomizing = isCustomizing,
                 customizingAnimationDuration = customizingAnimationDuration,
                 lifecycleOwner = lifecycleOwner,
-                modifier = Modifier.align(Alignment.CenterHorizontally),
+                modifier =
+                    Modifier.align(Alignment.CenterHorizontally).sysuiResTag("qs_footer_actions"),
             )
         }
         NotificationScrollingStack(
+            stackScrollView = notificationStackScrollView,
             viewModel = notificationsPlaceholderViewModel,
+            shadeSession = shadeSession,
             maxScrimTop = { screenHeight },
             shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
+            shadeMode = ShadeMode.Single,
             modifier =
                 Modifier.fillMaxWidth().offset { IntOffset(x = 0, y = screenHeight.roundToInt()) },
         )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/shared/SessionStorage.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/shared/SessionStorage.kt
new file mode 100644
index 0000000..dc58919
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/shared/SessionStorage.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.scene.session.shared
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+
+/** Data store for [Session][com.android.systemui.scene.session.ui.composable.Session]. */
+class SessionStorage {
+    private var _storage by mutableStateOf(hashMapOf<String, StorageEntry>())
+
+    /**
+     * Data store containing all state retained for invocations of
+     * [rememberSession][com.android.systemui.scene.session.ui.composable.Session.rememberSession]
+     */
+    val storage: MutableMap<String, StorageEntry>
+        get() = _storage
+
+    /**
+     * Storage for an individual invocation of
+     * [rememberSession][com.android.systemui.scene.session.ui.composable.Session.rememberSession]
+     */
+    class StorageEntry(val keys: Array<out Any?>, var stored: Any?)
+
+    /** Clears the data store; any downstream usage within `@Composable`s will be recomposed. */
+    fun clear() {
+        _storage = hashMapOf()
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt
new file mode 100644
index 0000000..924aa54
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt
@@ -0,0 +1,270 @@
+/*
+ * 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.scene.session.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.currentCompositeKeyHash
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.SaverScope
+import androidx.compose.runtime.saveable.mapSaver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import com.android.systemui.scene.session.shared.SessionStorage
+import com.android.systemui.util.kotlin.mapValuesNotNullTo
+
+/**
+ * An explicit storage for remembering composable state outside of the lifetime of a composition.
+ *
+ * Specifically, this allows easy conversion of standard
+ * [remember][androidx.compose.runtime.remember] invocations to ones that are preserved beyond the
+ * callsite's existence in the composition.
+ *
+ * ```kotlin
+ * @Composable
+ * fun Parent() {
+ *   val session = remember { Session() }
+ *   ...
+ *   if (someCondition) {
+ *     Child(session)
+ *   }
+ * }
+ *
+ * @Composable
+ * fun Child(session: Session) {
+ *   val state by session.rememberSession { mutableStateOf(0f) }
+ *   ...
+ * }
+ * ```
+ */
+interface Session {
+    /**
+     * Remember the value returned by [init] if all [inputs] are equal (`==`) to the values they had
+     * in the previous composition, otherwise produce and remember a new value by calling [init].
+     *
+     * @param inputs A set of inputs such that, when any of them have changed, will cause the state
+     *   to reset and [init] to be rerun
+     * @param key An optional key to be used as a key for the saved value. If `null`, we use the one
+     *   automatically generated by the Compose runtime which is unique for the every exact code
+     *   location in the composition tree
+     * @param init A factory function to create the initial value of this state
+     * @see androidx.compose.runtime.remember
+     */
+    @Composable fun <T> rememberSession(key: String?, vararg inputs: Any?, init: () -> T): T
+}
+
+/** Returns a new [Session], optionally backed by the provided [SessionStorage]. */
+fun Session(storage: SessionStorage = SessionStorage()): Session = SessionImpl(storage)
+
+/**
+ * Remember the value returned by [init] if all [inputs] are equal (`==`) to the values they had in
+ * the previous composition, otherwise produce and remember a new value by calling [init].
+ *
+ * @param inputs A set of inputs such that, when any of them have changed, will cause the state to
+ *   reset and [init] to be rerun
+ * @param key An optional key to be used as a key for the saved value. If not provided we use the
+ *   one automatically generated by the Compose runtime which is unique for the every exact code
+ *   location in the composition tree
+ * @param init A factory function to create the initial value of this state
+ * @see androidx.compose.runtime.remember
+ */
+@Composable
+fun <T> Session.rememberSession(vararg inputs: Any?, key: String? = null, init: () -> T): T =
+    rememberSession(key, inputs, init = init)
+
+/**
+ * An explicit storage for remembering composable state outside of the lifetime of a composition.
+ *
+ * Specifically, this allows easy conversion of standard [rememberSession] invocations to ones that
+ * are preserved beyond the callsite's existence in the composition.
+ *
+ * ```kotlin
+ * @Composable
+ * fun Parent() {
+ *   val session = rememberSaveableSession()
+ *   ...
+ *   if (someCondition) {
+ *     Child(session)
+ *   }
+ * }
+ *
+ * @Composable
+ * fun Child(session: SaveableSession) {
+ *   val state by session.rememberSaveableSession { mutableStateOf(0f) }
+ *   ...
+ * }
+ * ```
+ */
+interface SaveableSession : Session {
+    /**
+     * Remember the value produced by [init].
+     *
+     * It behaves similarly to [rememberSession], but the stored value will survive the activity or
+     * process recreation using the saved instance state mechanism (for example it happens when the
+     * screen is rotated in the Android application).
+     *
+     * @param inputs A set of inputs such that, when any of them have changed, will cause the state
+     *   to reset and [init] to be rerun
+     * @param saver The [Saver] object which defines how the state is saved and restored.
+     * @param key An optional key to be used as a key for the saved value. If not provided we use
+     *   the automatically generated by the Compose runtime which is unique for the every exact code
+     *   location in the composition tree
+     * @param init A factory function to create the initial value of this state
+     * @see rememberSaveable
+     */
+    @Composable
+    fun <T : Any> rememberSaveableSession(
+        vararg inputs: Any?,
+        saver: Saver<T, out Any>,
+        key: String?,
+        init: () -> T,
+    ): T
+}
+
+/**
+ * Returns a new [SaveableSession] that is preserved across configuration changes.
+ *
+ * @param inputs A set of inputs such that, when any of them have changed, will cause the state to
+ *   reset.
+ * @param key An optional key to be used as a key for the saved value. If not provided we use the
+ *   automatically generated by the Compose runtime which is unique for the every exact code
+ *   location in the composition tree.
+ */
+@Composable
+fun rememberSaveableSession(
+    vararg inputs: Any?,
+    key: String? = null,
+): SaveableSession =
+    rememberSaveable(inputs, SaveableSessionImpl.SessionSaver, key) { SaveableSessionImpl() }
+
+private class SessionImpl(
+    private val storage: SessionStorage = SessionStorage(),
+) : Session {
+    @Composable
+    override fun <T> rememberSession(key: String?, vararg inputs: Any?, init: () -> T): T {
+        val storage = storage.storage
+        val compositeKey = currentCompositeKeyHash
+        // key is the one provided by the user or the one generated by the compose runtime
+        val finalKey =
+            if (!key.isNullOrEmpty()) {
+                key
+            } else {
+                compositeKey.toString(MAX_SUPPORTED_RADIX)
+            }
+        if (finalKey !in storage) {
+            val value = init()
+            SideEffect { storage[finalKey] = SessionStorage.StorageEntry(inputs, value) }
+            return value
+        }
+        val entry = storage[finalKey]!!
+        if (!inputs.contentEquals(entry.keys)) {
+            val value = init()
+            SideEffect { entry.stored = value }
+            return value
+        }
+        @Suppress("UNCHECKED_CAST") return entry.stored as T
+    }
+}
+
+private class SaveableSessionImpl(
+    saveableStorage: MutableMap<String, StorageEntry> = mutableMapOf(),
+    sessionStorage: SessionStorage = SessionStorage(),
+) : SaveableSession, Session by Session(sessionStorage) {
+
+    var saveableStorage: MutableMap<String, StorageEntry> by mutableStateOf(saveableStorage)
+
+    @Composable
+    override fun <T : Any> rememberSaveableSession(
+        vararg inputs: Any?,
+        saver: Saver<T, out Any>,
+        key: String?,
+        init: () -> T,
+    ): T {
+        val compositeKey = currentCompositeKeyHash
+        // key is the one provided by the user or the one generated by the compose runtime
+        val finalKey =
+            if (!key.isNullOrEmpty()) {
+                key
+            } else {
+                compositeKey.toString(MAX_SUPPORTED_RADIX)
+            }
+
+        @Suppress("UNCHECKED_CAST") (saver as Saver<T, Any>)
+
+        if (finalKey !in saveableStorage) {
+            val value = init()
+            SideEffect { saveableStorage[finalKey] = StorageEntry.Restored(inputs, value, saver) }
+            return value
+        }
+        when (val entry = saveableStorage[finalKey]!!) {
+            is StorageEntry.Unrestored -> {
+                val value = saver.restore(entry.unrestored) ?: init()
+                SideEffect {
+                    saveableStorage[finalKey] = StorageEntry.Restored(inputs, value, saver)
+                }
+                return value
+            }
+            is StorageEntry.Restored<*> -> {
+                if (!inputs.contentEquals(entry.inputs)) {
+                    val value = init()
+                    SideEffect {
+                        saveableStorage[finalKey] = StorageEntry.Restored(inputs, value, saver)
+                    }
+                    return value
+                }
+                @Suppress("UNCHECKED_CAST") return entry.stored as T
+            }
+        }
+    }
+
+    sealed class StorageEntry {
+        class Unrestored(val unrestored: Any) : StorageEntry()
+
+        class Restored<T>(val inputs: Array<out Any?>, var stored: T, val saver: Saver<T, Any>) :
+            StorageEntry() {
+            fun SaverScope.saveEntry() {
+                with(saver) { stored?.let { save(it) } }
+            }
+        }
+    }
+
+    object SessionSaver :
+        Saver<SaveableSessionImpl, Any> by mapSaver(
+            save = { sessionScope: SaveableSessionImpl ->
+                sessionScope.saveableStorage.mapValues { (k, v) ->
+                    when (v) {
+                        is StorageEntry.Unrestored -> v.unrestored
+                        is StorageEntry.Restored<*> -> {
+                            with(v) { saveEntry() }
+                        }
+                    }
+                }
+            },
+            restore = { savedMap: Map<String, Any?> ->
+                SaveableSessionImpl(
+                    saveableStorage =
+                        savedMap.mapValuesNotNullTo(mutableMapOf()) { (k, v) ->
+                            v?.let { StorageEntry.Unrestored(v) }
+                        }
+                )
+            }
+        )
+}
+
+private const val MAX_SUPPORTED_RADIX = 36
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 7af9b7b..92b2b4e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -23,7 +23,6 @@
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
@@ -34,6 +33,7 @@
 import androidx.compose.ui.input.pointer.PointerEventPass
 import androidx.compose.ui.input.pointer.motionEventSpy
 import androidx.compose.ui.input.pointer.pointerInput
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayout
@@ -68,8 +68,9 @@
     modifier: Modifier = Modifier,
 ) {
     val coroutineScope = rememberCoroutineScope()
-    val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState()
-    val currentDestinations by viewModel.currentDestinationScenes(coroutineScope).collectAsState()
+    val currentSceneKey: SceneKey by viewModel.currentScene.collectAsStateWithLifecycle()
+    val currentDestinations by
+        viewModel.currentDestinationScenes(coroutineScope).collectAsStateWithLifecycle()
     val state: MutableSceneTransitionLayoutState = remember {
         MutableSceneTransitionLayoutState(
             initialScene = currentSceneKey,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index d7b10a9..cbaa894 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -5,16 +5,18 @@
 import com.android.systemui.bouncer.ui.composable.Bouncer
 import com.android.systemui.notifications.ui.composable.Notifications
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.shared.model.TransitionKeys.CollapseShadeInstantly
 import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse
+import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
 import com.android.systemui.scene.ui.composable.transitions.bouncerToGoneTransition
 import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsTransition
 import com.android.systemui.scene.ui.composable.transitions.goneToShadeTransition
+import com.android.systemui.scene.ui.composable.transitions.goneToSplitShadeTransition
 import com.android.systemui.scene.ui.composable.transitions.lockscreenToBouncerTransition
 import com.android.systemui.scene.ui.composable.transitions.lockscreenToCommunalTransition
 import com.android.systemui.scene.ui.composable.transitions.lockscreenToGoneTransition
 import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsTransition
 import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToSplitShadeTransition
 import com.android.systemui.scene.ui.composable.transitions.shadeToQuickSettingsTransition
 import com.android.systemui.shade.ui.composable.Shade
 
@@ -39,16 +41,9 @@
     from(
         Scenes.Gone,
         to = Scenes.Shade,
-        key = CollapseShadeInstantly,
+        key = ToSplitShade,
     ) {
-        goneToShadeTransition(durationScale = 0.0)
-    }
-    from(
-        Scenes.Gone,
-        to = Scenes.QuickSettings,
-        key = CollapseShadeInstantly,
-    ) {
-        goneToQuickSettingsTransition(durationScale = 0.0)
+        goneToSplitShadeTransition()
     }
     from(
         Scenes.Gone,
@@ -64,9 +59,9 @@
     from(
         Scenes.Lockscreen,
         to = Scenes.Shade,
-        key = CollapseShadeInstantly,
+        key = ToSplitShade,
     ) {
-        lockscreenToShadeTransition(durationScale = 0.0)
+        lockscreenToSplitShadeTransition()
     }
     from(
         Scenes.Lockscreen,
@@ -90,5 +85,9 @@
             Notifications.Elements.NotificationScrim,
             y = { Shade.Dimensions.ScrimOverscrollLimit }
         )
+        translate(
+            Shade.Elements.SplitShadeStartColumn,
+            y = { Shade.Dimensions.ScrimOverscrollLimit }
+        )
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
index 05f8f4b..4b4b7ed 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
@@ -62,4 +62,10 @@
             coroutineScope = coroutineScope,
         )
     }
+
+    override fun snapToScene(toScene: SceneKey) {
+        state.snapToScene(
+            scene = toScene,
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
index bf22563..f14ff76 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.scene.ui.composable.transitions
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.compose.animation.scene.TransitionBuilder
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
+fun TransitionBuilder.goneToSplitShadeTransition(
+    durationScale: Double = 1.0,
+) {
+    toSplitShadeTransition(durationScale)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
index bf22563..70c343c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.scene.ui.composable.transitions
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.compose.animation.scene.TransitionBuilder
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
+fun TransitionBuilder.lockscreenToSplitShadeTransition(
+    durationScale: Double = 1.0,
+) {
+    toSplitShadeTransition(durationScale = durationScale)
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToSplitShadeTransition.kt
new file mode 100644
index 0000000..a8315c0
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToSplitShadeTransition.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.unit.IntSize
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.UserActionDistance
+import com.android.compose.animation.scene.UserActionDistanceScope
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.ui.composable.QuickSettings
+import com.android.systemui.shade.ui.composable.Shade
+import com.android.systemui.shade.ui.composable.ShadeHeader
+import kotlin.time.Duration.Companion.milliseconds
+
+fun TransitionBuilder.toSplitShadeTransition(
+    durationScale: Double = 1.0,
+) {
+    spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
+    swipeSpec =
+        spring(
+            stiffness = Spring.StiffnessMediumLow,
+            visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
+        )
+    distance =
+        object : UserActionDistance {
+            override fun UserActionDistanceScope.absoluteDistance(
+                fromSceneSize: IntSize,
+                orientation: Orientation,
+            ): Float {
+                return fromSceneSize.height.toFloat() * 2 / 3f
+            }
+        }
+
+    fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) }
+
+    fractionRange(start = .33f) {
+        fade(ShadeHeader.Elements.Clock)
+        fade(ShadeHeader.Elements.CollapsedContentStart)
+        fade(ShadeHeader.Elements.CollapsedContentEnd)
+        fade(ShadeHeader.Elements.PrivacyChip)
+        fade(QuickSettings.Elements.SplitShadeQuickSettings)
+        fade(QuickSettings.Elements.FooterActions)
+        fade(Notifications.Elements.NotificationScrim)
+    }
+}
+
+private val DefaultDuration = 500.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index d528736..00ef11d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -30,13 +30,13 @@
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ReadOnlyComposable
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.LowestZIndexScenePicker
 import com.android.compose.animation.scene.SceneScope
@@ -51,7 +51,7 @@
     modifier: Modifier = Modifier,
     content: @Composable () -> Unit,
 ) {
-    val backgroundScene by viewModel.backgroundScene.collectAsState()
+    val backgroundScene by viewModel.backgroundScene.collectAsStateWithLifecycle()
 
     Box(modifier) {
         if (backgroundScene == Scenes.Lockscreen) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index 0bd38a1..ac3e015 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -35,7 +35,6 @@
 import androidx.compose.material3.ColorScheme
 import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
@@ -50,7 +49,9 @@
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.max
 import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.LowestZIndexScenePicker
 import com.android.compose.animation.scene.SceneScope
@@ -63,6 +64,7 @@
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
 import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
+import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.privacy.OngoingPrivacyChip
 import com.android.systemui.res.R
@@ -77,6 +79,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernShadeCarrierGroupMobileView
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.ShadeCarrierGroupMobileIconViewModel
 import com.android.systemui.statusbar.policy.Clock
+import kotlin.math.max
 
 object ShadeHeader {
     object Elements {
@@ -115,13 +118,17 @@
     statusBarIconController: StatusBarIconController,
     modifier: Modifier = Modifier,
 ) {
-    val isDisabled by viewModel.isDisabled.collectAsState()
+    val isDisabled by viewModel.isDisabled.collectAsStateWithLifecycle()
     if (isDisabled) {
         return
     }
 
     val cutoutWidth = LocalDisplayCutout.current.width()
+    val cutoutHeight = LocalDisplayCutout.current.height()
+    val cutoutTop = LocalDisplayCutout.current.top
     val cutoutLocation = LocalDisplayCutout.current.location
+    val horizontalPadding =
+        max(LocalScreenCornerRadius.current / 2f, Shade.Dimensions.HorizontalPadding)
 
     val useExpandedFormat by
         remember(cutoutLocation) {
@@ -131,7 +138,7 @@
             }
         }
 
-    val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState()
+    val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle()
 
     // This layout assumes it is globally positioned at (0, 0) and is the
     // same size as the screen.
@@ -140,7 +147,7 @@
         contents =
             listOf(
                 {
-                    Row {
+                    Row(modifier = Modifier.padding(horizontal = horizontalPadding)) {
                         Clock(
                             scale = 1f,
                             viewModel = viewModel,
@@ -157,7 +164,12 @@
                 },
                 {
                     if (isPrivacyChipVisible) {
-                        Box(modifier = Modifier.height(CollapsedHeight).fillMaxWidth()) {
+                        Box(
+                            modifier =
+                                Modifier.height(CollapsedHeight)
+                                    .fillMaxWidth()
+                                    .padding(horizontal = horizontalPadding)
+                        ) {
                             PrivacyChip(
                                 viewModel = viewModel,
                                 modifier = Modifier.align(Alignment.CenterEnd),
@@ -166,9 +178,13 @@
                     } else {
                         Row(
                             horizontalArrangement = Arrangement.End,
-                            modifier = Modifier.element(ShadeHeader.Elements.CollapsedContentEnd)
+                            modifier =
+                                Modifier.element(ShadeHeader.Elements.CollapsedContentEnd)
+                                    .padding(horizontal = horizontalPadding)
                         ) {
-                            SystemIconContainer {
+                            SystemIconContainer(
+                                modifier = Modifier.align(Alignment.CenterVertically)
+                            ) {
                                 when (LocalWindowSizeClass.current.widthSizeClass) {
                                     WindowWidthSizeClass.Medium,
                                     WindowWidthSizeClass.Expanded ->
@@ -206,7 +222,7 @@
 
         val screenWidth = constraints.maxWidth
         val cutoutWidthPx = cutoutWidth.roundToPx()
-        val height = CollapsedHeight.roundToPx()
+        val height = max(cutoutHeight + (cutoutTop * 2), CollapsedHeight).roundToPx()
         val childConstraints = Constraints.fixed((screenWidth - cutoutWidthPx) / 2, height)
 
         val startMeasurable = measurables[0][0]
@@ -255,7 +271,7 @@
     statusBarIconController: StatusBarIconController,
     modifier: Modifier = Modifier,
 ) {
-    val isDisabled by viewModel.isDisabled.collectAsState()
+    val isDisabled by viewModel.isDisabled.collectAsStateWithLifecycle()
     if (isDisabled) {
         return
     }
@@ -264,7 +280,7 @@
         derivedStateOf { shouldUseExpandedFormat(layoutState.transitionState) }
     }
 
-    val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState()
+    val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle()
 
     Box(modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root)) {
         if (isPrivacyChipVisible) {
@@ -419,7 +435,7 @@
     modifier: Modifier = Modifier,
 ) {
     Row(modifier = modifier) {
-        val subIds by viewModel.mobileSubIds.collectAsState()
+        val subIds by viewModel.mobileSubIds.collectAsStateWithLifecycle()
 
         for (subId in subIds) {
             Spacer(modifier = Modifier.width(5.dp))
@@ -456,10 +472,12 @@
     val micSlot = stringResource(id = com.android.internal.R.string.status_bar_microphone)
     val locationSlot = stringResource(id = com.android.internal.R.string.status_bar_location)
 
-    val isSingleCarrier by viewModel.isSingleCarrier.collectAsState()
-    val isPrivacyChipEnabled by viewModel.isPrivacyChipEnabled.collectAsState()
-    val isMicCameraIndicationEnabled by viewModel.isMicCameraIndicationEnabled.collectAsState()
-    val isLocationIndicationEnabled by viewModel.isLocationIndicationEnabled.collectAsState()
+    val isSingleCarrier by viewModel.isSingleCarrier.collectAsStateWithLifecycle()
+    val isPrivacyChipEnabled by viewModel.isPrivacyChipEnabled.collectAsStateWithLifecycle()
+    val isMicCameraIndicationEnabled by
+        viewModel.isMicCameraIndicationEnabled.collectAsStateWithLifecycle()
+    val isLocationIndicationEnabled by
+        viewModel.isLocationIndicationEnabled.collectAsStateWithLifecycle()
 
     AndroidView(
         factory = { context ->
@@ -528,7 +546,7 @@
     viewModel: ShadeHeaderViewModel,
     modifier: Modifier = Modifier,
 ) {
-    val privacyList by viewModel.privacyItems.collectAsState()
+    val privacyList by viewModel.privacyItems.collectAsStateWithLifecycle()
 
     AndroidView(
         factory = { context ->
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index ef5d4e1..a0278a6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -31,6 +31,7 @@
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.displayCutoutPadding
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -44,7 +45,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -57,6 +57,7 @@
 import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.res.colorResource
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.LowestZIndexScenePicker
 import com.android.compose.animation.scene.SceneScope
@@ -67,6 +68,10 @@
 import com.android.compose.modifiers.padding
 import com.android.compose.modifiers.thenIf
 import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
+import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
+import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
+import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.media.controls.ui.composable.MediaCarousel
 import com.android.systemui.media.controls.ui.controller.MediaCarouselController
@@ -79,13 +84,16 @@
 import com.android.systemui.qs.ui.composable.BrightnessMirror
 import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.res.R
+import com.android.systemui.scene.session.ui.composable.SaveableSession
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.ComposableScene
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
 import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController
 import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import dagger.Lazy
 import javax.inject.Inject
 import javax.inject.Named
 import kotlin.math.roundToInt
@@ -96,6 +104,7 @@
         val MediaCarousel = ElementKey("ShadeMediaCarousel")
         val BackgroundScrim =
             ElementKey("ShadeBackgroundScrim", scenePicker = LowestZIndexScenePicker)
+        val SplitShadeStartColumn = ElementKey("SplitShadeStartColumn")
     }
 
     object Dimensions {
@@ -119,6 +128,8 @@
 class ShadeScene
 @Inject
 constructor(
+    private val shadeSession: SaveableSession,
+    private val notificationStackScrollView: Lazy<NotificationScrollView>,
     private val viewModel: ShadeSceneViewModel,
     private val tintedIconManagerFactory: TintedIconManager.Factory,
     private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
@@ -126,6 +137,7 @@
     private val mediaCarouselController: MediaCarouselController,
     @Named(QUICK_QS_PANEL) private val mediaHost: MediaHost,
 ) : ComposableScene {
+
     override val key = Scenes.Shade
 
     override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
@@ -136,6 +148,7 @@
         modifier: Modifier,
     ) =
         ShadeScene(
+            notificationStackScrollView.get(),
             viewModel = viewModel,
             createTintedIconManager = tintedIconManagerFactory::create,
             createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
@@ -143,6 +156,7 @@
             mediaCarouselController = mediaCarouselController,
             mediaHost = mediaHost,
             modifier = modifier,
+            shadeSession = shadeSession,
         )
 
     init {
@@ -154,6 +168,7 @@
 
 @Composable
 private fun SceneScope.ShadeScene(
+    notificationStackScrollView: NotificationScrollView,
     viewModel: ShadeSceneViewModel,
     createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
     createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -161,11 +176,13 @@
     mediaCarouselController: MediaCarouselController,
     mediaHost: MediaHost,
     modifier: Modifier = Modifier,
+    shadeSession: SaveableSession,
 ) {
-    val shadeMode by viewModel.shadeMode.collectAsState()
+    val shadeMode by viewModel.shadeMode.collectAsStateWithLifecycle()
     when (shadeMode) {
         is ShadeMode.Single ->
             SingleShade(
+                notificationStackScrollView = notificationStackScrollView,
                 viewModel = viewModel,
                 createTintedIconManager = createTintedIconManager,
                 createBatteryMeterViewController = createBatteryMeterViewController,
@@ -173,9 +190,11 @@
                 mediaCarouselController = mediaCarouselController,
                 mediaHost = mediaHost,
                 modifier = modifier,
+                shadeSession = shadeSession,
             )
         is ShadeMode.Split ->
             SplitShade(
+                notificationStackScrollView = notificationStackScrollView,
                 viewModel = viewModel,
                 createTintedIconManager = createTintedIconManager,
                 createBatteryMeterViewController = createBatteryMeterViewController,
@@ -183,6 +202,7 @@
                 mediaCarouselController = mediaCarouselController,
                 mediaHost = mediaHost,
                 modifier = modifier,
+                shadeSession = shadeSession,
             )
         is ShadeMode.Dual -> error("Dual shade is not yet implemented!")
     }
@@ -190,6 +210,7 @@
 
 @Composable
 private fun SceneScope.SingleShade(
+    notificationStackScrollView: NotificationScrollView,
     viewModel: ShadeSceneViewModel,
     createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
     createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -197,7 +218,10 @@
     mediaCarouselController: MediaCarouselController,
     mediaHost: MediaHost,
     modifier: Modifier = Modifier,
+    shadeSession: SaveableSession,
 ) {
+    val cutoutLocation = LocalDisplayCutout.current.location
+
     val maxNotifScrimTop = remember { mutableStateOf(0f) }
     val tileSquishiness by
         animateSceneFloatAsState(
@@ -205,7 +229,8 @@
             key = QuickSettings.SharedValues.TilesSquishiness,
             canOverflow = false
         )
-    val isClickable by viewModel.isClickable.collectAsState()
+    val isClickable by viewModel.isClickable.collectAsStateWithLifecycle()
+    val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle()
 
     val shouldPunchHoleBehindScrim =
         layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade) ||
@@ -233,19 +258,21 @@
                         Column(
                             horizontalAlignment = Alignment.CenterHorizontally,
                             modifier =
-                                Modifier.fillMaxWidth().thenIf(isClickable) {
-                                    Modifier.clickable(onClick = { viewModel.onContentClicked() })
-                                }
+                                Modifier.fillMaxWidth()
+                                    .thenIf(isClickable) {
+                                        Modifier.clickable(
+                                            onClick = { viewModel.onContentClicked() }
+                                        )
+                                    }
+                                    .thenIf(cutoutLocation != CutoutLocation.CENTER) {
+                                        Modifier.displayCutoutPadding()
+                                    },
                         ) {
                             CollapsedShadeHeader(
                                 viewModel = viewModel.shadeHeaderViewModel,
                                 createTintedIconManager = createTintedIconManager,
                                 createBatteryMeterViewController = createBatteryMeterViewController,
                                 statusBarIconController = statusBarIconController,
-                                modifier =
-                                    Modifier.padding(
-                                        horizontal = Shade.Dimensions.HorizontalPadding
-                                    )
                             )
                             Box(Modifier.element(QuickSettings.Elements.QuickQuickSettings)) {
                                 QuickSettings(
@@ -257,7 +284,7 @@
                             }
 
                             MediaCarousel(
-                                isVisible = viewModel::isMediaVisible,
+                                isVisible = isMediaVisible,
                                 mediaHost = mediaHost,
                                 modifier = Modifier.fillMaxWidth(),
                                 carouselController = mediaCarouselController,
@@ -268,8 +295,11 @@
                     },
                     {
                         NotificationScrollingStack(
+                            shadeSession = shadeSession,
+                            stackScrollView = notificationStackScrollView,
                             viewModel = viewModel.notifications,
                             maxScrimTop = { maxNotifScrimTop.value },
+                            shadeMode = ShadeMode.Single,
                             shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
                         )
                     },
@@ -294,6 +324,7 @@
 
 @Composable
 private fun SceneScope.SplitShade(
+    notificationStackScrollView: NotificationScrollView,
     viewModel: ShadeSceneViewModel,
     createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
     createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -301,28 +332,36 @@
     mediaCarouselController: MediaCarouselController,
     mediaHost: MediaHost,
     modifier: Modifier = Modifier,
+    shadeSession: SaveableSession,
 ) {
-    val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
-    val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsState()
+    val screenCornerRadius = LocalScreenCornerRadius.current
+
+    val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle()
+    val isCustomizerShowing by
+        viewModel.qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle()
     val customizingAnimationDuration by
-        viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsState()
+        viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsStateWithLifecycle()
     val lifecycleOwner = LocalLifecycleOwner.current
     val footerActionsViewModel =
         remember(lifecycleOwner, viewModel) { viewModel.getFooterActionsViewModel(lifecycleOwner) }
     val tileSquishiness by
-        animateSceneFloatAsState(value = 1f, key = QuickSettings.SharedValues.TilesSquishiness)
+        animateSceneFloatAsState(
+            value = 1f,
+            key = QuickSettings.SharedValues.TilesSquishiness,
+            canOverflow = false,
+        )
     val unfoldTranslationXForStartSide by
         viewModel
             .unfoldTranslationX(
                 isOnStartSide = true,
             )
-            .collectAsState(0f)
+            .collectAsStateWithLifecycle(0f)
     val unfoldTranslationXForEndSide by
         viewModel
             .unfoldTranslationX(
                 isOnStartSide = false,
             )
-            .collectAsState(0f)
+            .collectAsStateWithLifecycle(0f)
 
     val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
     val bottomPadding by
@@ -346,7 +385,8 @@
         }
     }
 
-    val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState()
+    val brightnessMirrorShowing by
+        viewModel.brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle()
     val contentAlpha by
         animateFloatAsState(
             targetValue = if (brightnessMirrorShowing) 0f else 1f,
@@ -356,6 +396,8 @@
     viewModel.notifications.setAlphaForBrightnessMirror(contentAlpha)
     DisposableEffect(Unit) { onDispose { viewModel.notifications.setAlphaForBrightnessMirror(1f) } }
 
+    val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle()
+
     val brightnessMirrorShowingModifier = Modifier.graphicsLayer { alpha = contentAlpha }
 
     Box(
@@ -378,8 +420,7 @@
                 createBatteryMeterViewController = createBatteryMeterViewController,
                 statusBarIconController = statusBarIconController,
                 modifier =
-                    Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
-                        .then(brightnessMirrorShowingModifier)
+                    Modifier.then(brightnessMirrorShowingModifier)
                         .padding(
                             horizontal = { unfoldTranslationXForStartSide.roundToInt() },
                         )
@@ -388,9 +429,9 @@
             Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
                 Box(
                     modifier =
-                        Modifier.weight(1f).graphicsLayer {
-                            translationX = unfoldTranslationXForStartSide
-                        },
+                        Modifier.element(Shade.Elements.SplitShadeStartColumn)
+                            .weight(1f)
+                            .graphicsLayer { translationX = unfoldTranslationXForStartSide },
                 ) {
                     BrightnessMirror(
                         viewModel = viewModel.brightnessMirrorViewModel,
@@ -406,6 +447,7 @@
                         Column(
                             modifier =
                                 Modifier.fillMaxSize()
+                                    .sysuiResTag("expanded_qs_scroll_view")
                                     .weight(1f)
                                     .thenIf(!isCustomizerShowing) {
                                         Modifier.verticalNestedScrollToScene()
@@ -431,7 +473,7 @@
                             }
 
                             MediaCarousel(
-                                isVisible = viewModel::isMediaVisible,
+                                isVisible = isMediaVisible,
                                 mediaHost = mediaHost,
                                 modifier = Modifier.fillMaxWidth(),
                                 carouselController = mediaCarouselController,
@@ -444,19 +486,23 @@
                             lifecycleOwner = lifecycleOwner,
                             modifier =
                                 Modifier.align(Alignment.CenterHorizontally)
+                                    .sysuiResTag("qs_footer_actions")
                                     .then(brightnessMirrorShowingModifier),
                         )
                     }
                 }
 
                 NotificationScrollingStack(
+                    shadeSession = shadeSession,
+                    stackScrollView = notificationStackScrollView,
                     viewModel = viewModel.notifications,
                     maxScrimTop = { 0f },
                     shouldPunchHoleBehindScrim = false,
+                    shadeMode = ShadeMode.Split,
                     modifier =
                         Modifier.weight(1f)
                             .fillMaxHeight()
-                            .padding(bottom = navBarBottomHeight)
+                            .padding(end = screenCornerRadius / 2f, bottom = navBarBottomHeight)
                             .then(brightnessMirrorShowingModifier)
                 )
             }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt
index 5e107c6..931ff56 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt
@@ -3,9 +3,9 @@
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.shadeHeaderText
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
 
@@ -14,8 +14,8 @@
     viewModel: ShadeHeaderViewModel,
     modifier: Modifier = Modifier,
 ) {
-    val longerText = viewModel.longerDateText.collectAsState()
-    val shorterText = viewModel.shorterDateText.collectAsState()
+    val longerText = viewModel.longerDateText.collectAsStateWithLifecycle()
+    val shorterText = viewModel.shorterDateText.collectAsStateWithLifecycle()
 
     Layout(
         contents =
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt
index 00225fc..3976c61 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt
@@ -16,29 +16,40 @@
 
 package com.android.systemui.volume.panel.component.anc.ui.composable
 
+import android.view.Gravity
+import androidx.compose.foundation.basicMarquee
 import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonColors
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.LiveRegionMode
 import androidx.compose.ui.semantics.clearAndSetSemantics
 import androidx.compose.ui.semantics.contentDescription
-import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.liveRegion
 import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.systemui.res.R
 import com.android.systemui.volume.panel.component.anc.ui.viewmodel.AncViewModel
+import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup
 import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
 import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
 import javax.inject.Inject
@@ -52,32 +63,52 @@
 
     @Composable
     override fun VolumePanelComposeScope.Content(modifier: Modifier) {
-        val slice by viewModel.buttonSlice.collectAsState()
+        val slice by viewModel.buttonSlice.collectAsStateWithLifecycle()
         val label = stringResource(R.string.volume_panel_noise_control_title)
+        val screenWidth: Float =
+            with(LocalDensity.current) { LocalConfiguration.current.screenWidthDp.dp.toPx() }
+        var gravity by remember { mutableIntStateOf(Gravity.CENTER_HORIZONTAL) }
+        val isClickable = viewModel.isClickable(slice)
         Column(
-            modifier = modifier,
+            modifier =
+                modifier.onGloballyPositioned {
+                    gravity = VolumePanelPopup.calculateGravity(it, screenWidth)
+                },
             verticalArrangement = Arrangement.spacedBy(12.dp),
             horizontalAlignment = Alignment.CenterHorizontally,
         ) {
-            SliceAndroidView(
-                modifier =
-                    Modifier.height(64.dp)
-                        .fillMaxWidth()
-                        .semantics {
-                            role = Role.Button
+            Box(
+                modifier = Modifier.height(64.dp),
+            ) {
+                SliceAndroidView(
+                    modifier = modifier.fillMaxSize(),
+                    slice = slice,
+                    onWidthChanged = viewModel::onButtonSliceWidthChanged,
+                    enableAccessibility = false,
+                )
+                Button(
+                    modifier =
+                        modifier.fillMaxSize().padding(8.dp).semantics {
+                            liveRegion = LiveRegionMode.Polite
                             contentDescription = label
-                        }
-                        .clip(RoundedCornerShape(28.dp)),
-                slice = slice,
-                onWidthChanged = viewModel::onButtonSliceWidthChanged,
-                onClick = { ancPopup.show(null) }
-            )
+                        },
+                    enabled = isClickable,
+                    onClick = { with(ancPopup) { show(null, gravity) } },
+                    colors =
+                        ButtonColors(
+                            contentColor = Color.Transparent,
+                            containerColor = Color.Transparent,
+                            disabledContentColor = Color.Transparent,
+                            disabledContainerColor = Color.Transparent,
+                        )
+                ) {}
+            }
+
             Text(
-                modifier = Modifier.clearAndSetSemantics {},
+                modifier = Modifier.clearAndSetSemantics {}.basicMarquee(),
                 text = label,
                 style = MaterialTheme.typography.labelMedium,
-                maxLines = 1,
-                overflow = TextOverflow.Ellipsis,
+                maxLines = 2,
             )
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
index e1ee01e..15df1be 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
@@ -16,17 +16,18 @@
 
 package com.android.systemui.volume.panel.component.anc.ui.composable
 
+import android.view.Gravity
 import androidx.compose.foundation.basicMarquee
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextAlign
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.slice.Slice
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.animation.Expandable
@@ -47,9 +48,10 @@
 ) {
 
     /** Shows a popup with the [expandable] animation. */
-    fun show(expandable: Expandable?) {
+    fun show(expandable: Expandable?, horizontalGravity: Int) {
         uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_ANC_POPUP_SHOWN)
-        volumePanelPopup.show(expandable, { Title() }, { Content(it) })
+        val gravity = horizontalGravity or Gravity.BOTTOM
+        volumePanelPopup.show(expandable, gravity, { Title() }, { Content(it) })
     }
 
     @Composable
@@ -65,14 +67,14 @@
 
     @Composable
     private fun Content(dialog: SystemUIDialog) {
-        val isAvailable by viewModel.isAvailable.collectAsState(true)
+        val isAvailable by viewModel.isAvailable.collectAsStateWithLifecycle(true)
 
         if (!isAvailable) {
             SideEffect { dialog.dismiss() }
             return
         }
 
-        val slice by viewModel.popupSlice.collectAsState()
+        val slice by viewModel.popupSlice.collectAsStateWithLifecycle()
         SliceAndroidView(
             modifier = Modifier.fillMaxWidth(),
             slice = slice,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
index 74af3ca..23d50c5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
@@ -16,11 +16,12 @@
 
 package com.android.systemui.volume.panel.component.anc.ui.composable
 
-import android.annotation.SuppressLint
 import android.content.Context
+import android.os.Bundle
 import android.view.ContextThemeWrapper
-import android.view.MotionEvent
 import android.view.View
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityNodeInfo
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.viewinterop.AndroidView
@@ -33,29 +34,31 @@
     slice: Slice?,
     modifier: Modifier = Modifier,
     onWidthChanged: ((Int) -> Unit)? = null,
-    onClick: (() -> Unit)? = null,
+    enableAccessibility: Boolean = true,
 ) {
     AndroidView(
         modifier = modifier,
         factory = { context: Context ->
-            ClickableSliceView(
+            ComposeSliceView(
                     ContextThemeWrapper(context, R.style.Widget_SliceView_VolumePanel),
-                    onClick,
                 )
                 .apply {
                     mode = SliceView.MODE_LARGE
                     isScrollable = false
                     importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
                     setShowTitleItems(true)
-                    if (onWidthChanged != null) {
-                        addOnLayoutChangeListener(OnWidthChangedLayoutListener(onWidthChanged))
-                    }
-                    if (onClick != null) {
-                        setOnClickListener { onClick() }
-                    }
                 }
         },
-        update = { sliceView: SliceView -> sliceView.slice = slice }
+        update = { sliceView: ComposeSliceView ->
+            sliceView.slice = slice
+            sliceView.layoutListener = onWidthChanged?.let(::OnWidthChangedLayoutListener)
+            sliceView.enableAccessibility = enableAccessibility
+        },
+        onRelease = { sliceView: ComposeSliceView ->
+            sliceView.layoutListener = null
+            sliceView.slice = null
+            sliceView.enableAccessibility = true
+        },
     )
 }
 
@@ -81,27 +84,39 @@
     }
 }
 
-/**
- * [SliceView] that prioritises [onClick] when its clicked instead of passing the event to the slice
- * first.
- */
-@SuppressLint("ViewConstructor") // only used in this class
-private class ClickableSliceView(
-    context: Context,
-    private val onClick: (() -> Unit)?,
-) : SliceView(context) {
+private class ComposeSliceView(context: Context) : SliceView(context) {
 
-    init {
-        if (onClick != null) {
-            setOnClickListener {}
+    var enableAccessibility: Boolean = true
+    var layoutListener: OnLayoutChangeListener? = null
+        set(value) {
+            field?.let { removeOnLayoutChangeListener(it) }
+            field = value
+            field?.let { addOnLayoutChangeListener(it) }
+        }
+
+    override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo?) {
+        if (enableAccessibility) {
+            super.onInitializeAccessibilityNodeInfo(info)
         }
     }
 
-    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
-        return (isSliceViewClickable && onClick != null) || super.onInterceptTouchEvent(ev)
+    override fun onInitializeAccessibilityEvent(event: AccessibilityEvent?) {
+        if (enableAccessibility) {
+            super.onInitializeAccessibilityEvent(event)
+        }
     }
 
-    override fun onClick(v: View?) {
-        onClick?.takeIf { isSliceViewClickable }?.let { it() } ?: super.onClick(v)
+    override fun performAccessibilityAction(action: Int, arguments: Bundle?): Boolean {
+        return if (enableAccessibility) {
+            super.performAccessibilityAction(action, arguments)
+        } else {
+            false
+        }
+    }
+
+    override fun addChildrenForAccessibility(outChildren: ArrayList<View>?) {
+        if (enableAccessibility) {
+            super.addChildrenForAccessibility(outChildren)
+        }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
index 0893b9d..e1ae80f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.volume.panel.component.button.ui.composable
 
+import android.view.Gravity
 import androidx.compose.foundation.basicMarquee
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
@@ -27,20 +28,27 @@
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.clearAndSetSemantics
 import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.role
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.Expandable
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
+import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup.Companion.calculateGravity
 import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
 import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
 import kotlinx.coroutines.flow.StateFlow
@@ -48,17 +56,22 @@
 /** [ComposeVolumePanelUiComponent] implementing a clickable button from a bottom row. */
 class ButtonComponent(
     private val viewModelFlow: StateFlow<ButtonViewModel?>,
-    private val onClick: (Expandable) -> Unit
+    private val onClick: (expandable: Expandable, horizontalGravity: Int) -> Unit
 ) : ComposeVolumePanelUiComponent {
 
     @Composable
     override fun VolumePanelComposeScope.Content(modifier: Modifier) {
-        val viewModelByState by viewModelFlow.collectAsState()
+        val viewModelByState by viewModelFlow.collectAsStateWithLifecycle()
         val viewModel = viewModelByState ?: return
         val label = viewModel.label.toString()
 
+        val screenWidth: Float =
+            with(LocalDensity.current) { LocalConfiguration.current.screenWidthDp.dp.toPx() }
+        var gravity by remember { mutableIntStateOf(Gravity.CENTER_HORIZONTAL) }
+
         Column(
-            modifier = modifier,
+            modifier =
+                modifier.onGloballyPositioned { gravity = calculateGravity(it, screenWidth) },
             verticalArrangement = Arrangement.spacedBy(12.dp),
             horizontalAlignment = Alignment.CenterHorizontally,
         ) {
@@ -82,7 +95,7 @@
                         } else {
                             MaterialTheme.colorScheme.onSurface
                         },
-                    onClick = onClick,
+                    onClick = { onClick(it, gravity) },
                 ) {
                     Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                         Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index 874c0a2..1b821d3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -19,6 +19,7 @@
 import androidx.compose.foundation.basicMarquee
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
@@ -28,7 +29,6 @@
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -41,6 +41,7 @@
 import androidx.compose.ui.semantics.toggleableState
 import androidx.compose.ui.state.ToggleableState
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
 import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
@@ -55,7 +56,7 @@
 
     @Composable
     override fun VolumePanelComposeScope.Content(modifier: Modifier) {
-        val viewModelByState by viewModelFlow.collectAsState()
+        val viewModelByState by viewModelFlow.collectAsStateWithLifecycle()
         val viewModel = viewModelByState ?: return
         val label = viewModel.label.toString()
 
@@ -91,7 +92,8 @@
                         },
                     onClick = { onCheckedChange(!viewModel.isActive) },
                     shape = RoundedCornerShape(28.dp),
-                    colors = colors
+                    colors = colors,
+                    contentPadding = PaddingValues(0.dp)
                 ) {
                     Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
                 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
index ded63a1..237bbfd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
@@ -45,7 +45,6 @@
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -55,6 +54,7 @@
 import androidx.compose.ui.semantics.onClick
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.Expandable
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.common.ui.compose.toColor
@@ -77,9 +77,9 @@
     @Composable
     override fun VolumePanelComposeScope.Content(modifier: Modifier) {
         val connectedDeviceViewModel: ConnectedDeviceViewModel? by
-            viewModel.connectedDeviceViewModel.collectAsState()
+            viewModel.connectedDeviceViewModel.collectAsStateWithLifecycle()
         val deviceIconViewModel: DeviceIconViewModel? by
-            viewModel.deviceIconViewModel.collectAsState()
+            viewModel.deviceIconViewModel.collectAsStateWithLifecycle()
         val clickLabel = stringResource(R.string.volume_panel_enter_media_output_settings)
 
         Expandable(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
index bb4e957..3b1bf2a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.volume.panel.component.popup.ui.composable
 
 import android.view.Gravity
+import androidx.annotation.GravityInt
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -31,6 +32,8 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.boundsInRoot
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.semantics.paneTitle
@@ -60,13 +63,14 @@
      */
     fun show(
         expandable: Expandable?,
+        @GravityInt gravity: Int,
         title: @Composable (SystemUIDialog) -> Unit,
         content: @Composable (SystemUIDialog) -> Unit,
     ) {
         val dialog =
             dialogFactory.create(
                 theme = R.style.Theme_VolumePanel_Popup,
-                dialogGravity = Gravity.BOTTOM,
+                dialogGravity = gravity,
             ) {
                 PopupComposable(it, title, content)
             }
@@ -122,4 +126,23 @@
             }
         }
     }
+
+    companion object {
+
+        /**
+         * Returns absolute ([Gravity.LEFT], [Gravity.RIGHT] or [Gravity.CENTER_HORIZONTAL])
+         * [GravityInt] for the popup based on the [coordinates] global position relative to the
+         * [screenWidthPx].
+         */
+        @GravityInt
+        fun calculateGravity(coordinates: LayoutCoordinates, screenWidthPx: Float): Int {
+            val bottomCenter: Float = coordinates.boundsInRoot().bottomCenter.x
+            val rootBottomCenter: Float = screenWidthPx / 2
+            return when {
+                bottomCenter < rootBottomCenter -> Gravity.LEFT
+                bottomCenter > rootBottomCenter -> Gravity.RIGHT
+                else -> Gravity.CENTER_HORIZONTAL
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
index 12d2bc2..9891b5b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
@@ -16,17 +16,18 @@
 
 package com.android.systemui.volume.panel.component.spatialaudio.ui.composable
 
+import android.view.Gravity
 import androidx.compose.foundation.basicMarquee
 import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextAlign
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.ui.compose.Icon
@@ -47,14 +48,15 @@
 ) {
 
     /** Shows a popup with the [expandable] animation. */
-    fun show(expandable: Expandable) {
+    fun show(expandable: Expandable, horizontalGravity: Int) {
         uiEventLogger.logWithPosition(
             VolumePanelUiEvent.VOLUME_PANEL_SPATIAL_AUDIO_POP_UP_SHOWN,
             0,
             null,
             viewModel.spatialAudioButtons.value.indexOfFirst { it.button.isActive }
         )
-        volumePanelPopup.show(expandable, { Title() }, { Content(it) })
+        val gravity = horizontalGravity or Gravity.BOTTOM
+        volumePanelPopup.show(expandable, gravity, { Title() }, { Content(it) })
     }
 
     @Composable
@@ -70,14 +72,14 @@
 
     @Composable
     private fun Content(dialog: SystemUIDialog) {
-        val isAvailable by viewModel.isAvailable.collectAsState()
+        val isAvailable by viewModel.isAvailable.collectAsStateWithLifecycle()
 
         if (!isAvailable) {
             SideEffect { dialog.dismiss() }
             return
         }
 
-        val enabledModelStates by viewModel.spatialAudioButtons.collectAsState()
+        val enabledModelStates by viewModel.spatialAudioButtons.collectAsStateWithLifecycle()
         if (enabledModelStates.isEmpty()) {
             return
         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index a3467f2..072e91a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -20,6 +20,8 @@
 import androidx.compose.animation.EnterTransition
 import androidx.compose.animation.ExitTransition
 import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.animateDpAsState
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.core.updateTransition
 import androidx.compose.animation.expandVertically
@@ -28,10 +30,8 @@
 import androidx.compose.animation.scaleIn
 import androidx.compose.animation.scaleOut
 import androidx.compose.animation.shrinkVertically
-import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
@@ -39,7 +39,7 @@
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.IconButtonDefaults
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -49,15 +49,22 @@
 import androidx.compose.ui.semantics.role
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.stateDescription
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.PlatformSliderColors
+import com.android.compose.modifiers.padding
 import com.android.systemui.res.R
 import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
 
 private const val EXPAND_DURATION_MILLIS = 500
+private const val COLLAPSE_EXPAND_BUTTON_DELAY_MILLIS = 350
 private const val COLLAPSE_DURATION_MILLIS = 300
+private const val EXPAND_BUTTON_ANIMATION_DURATION_MILLIS = 350
+private const val TOP_SLIDER_ANIMATION_DURATION_MILLIS = 400
 private const val SHRINK_FRACTION = 0.55f
 private const val SCALE_FRACTION = 0.9f
+private const val EXPAND_BUTTON_SCALE = 0.8f
 
 /** Volume sliders laid out in a collapsable column */
 @OptIn(ExperimentalAnimationApi::class)
@@ -73,14 +80,15 @@
     require(viewModels.isNotEmpty())
     val transition = updateTransition(isExpanded, label = "CollapsableSliders")
     Column(modifier = modifier) {
-        Row(
+        Box(
             modifier = Modifier.fillMaxWidth(),
-            horizontalArrangement = Arrangement.spacedBy(8.dp),
         ) {
             val sliderViewModel: SliderViewModel = viewModels.first()
-            val sliderState by viewModels.first().slider.collectAsState()
+            val sliderState by viewModels.first().slider.collectAsStateWithLifecycle()
+            val sliderPadding by topSliderPadding(isExpandable)
+
             VolumeSlider(
-                modifier = Modifier.weight(1f),
+                modifier = Modifier.padding(end = { sliderPadding.roundToPx() }).fillMaxWidth(),
                 state = sliderState,
                 onValueChange = { newValue: Float ->
                     sliderViewModel.onValueChanged(sliderState, newValue)
@@ -90,21 +98,13 @@
                 sliderColors = sliderColors,
             )
 
-            val expandButtonStateDescription =
-                if (isExpanded) stringResource(R.string.volume_panel_expanded_sliders)
-                else stringResource(R.string.volume_panel_collapsed_sliders)
-            if (isExpandable) {
-                ExpandButton(
-                    modifier =
-                        Modifier.semantics {
-                            role = Role.Switch
-                            stateDescription = expandButtonStateDescription
-                        },
-                    isExpanded = isExpanded,
-                    onExpandedChanged = onExpandedChanged,
-                    sliderColors = sliderColors,
-                )
-            }
+            ExpandButton(
+                modifier = Modifier.align(Alignment.CenterEnd),
+                isExpanded = isExpanded,
+                isExpandable = isExpandable,
+                onExpandedChanged = onExpandedChanged,
+                sliderColors = sliderColors,
+            )
         }
         transition.AnimatedVisibility(
             visible = { it || !isExpandable },
@@ -119,7 +119,7 @@
                 Column {
                     for (index in 1..viewModels.lastIndex) {
                         val sliderViewModel: SliderViewModel = viewModels[index]
-                        val sliderState by sliderViewModel.slider.collectAsState()
+                        val sliderState by sliderViewModel.slider.collectAsStateWithLifecycle()
                         transition.AnimatedVisibility(
                             modifier = Modifier.padding(top = 16.dp),
                             visible = { it || !isExpandable },
@@ -147,30 +147,48 @@
 @Composable
 private fun ExpandButton(
     isExpanded: Boolean,
+    isExpandable: Boolean,
     onExpandedChanged: (Boolean) -> Unit,
     sliderColors: PlatformSliderColors,
     modifier: Modifier = Modifier,
 ) {
-    IconButton(
-        modifier = modifier.size(64.dp),
-        onClick = { onExpandedChanged(!isExpanded) },
-        colors =
-            IconButtonDefaults.filledIconButtonColors(
-                containerColor = sliderColors.indicatorColor,
-                contentColor = sliderColors.iconColor
-            ),
+    val expandButtonStateDescription =
+        if (isExpanded) {
+            stringResource(R.string.volume_panel_expanded_sliders)
+        } else {
+            stringResource(R.string.volume_panel_collapsed_sliders)
+        }
+    AnimatedVisibility(
+        modifier = modifier,
+        visible = isExpandable,
+        enter = expandButtonEnterTransition(),
+        exit = expandButtonExitTransition(),
     ) {
-        Icon(
-            painter =
-                painterResource(
-                    if (isExpanded) {
-                        R.drawable.ic_filled_arrow_down
-                    } else {
-                        R.drawable.ic_filled_arrow_up
-                    }
+        IconButton(
+            modifier =
+                Modifier.size(64.dp).semantics {
+                    role = Role.Switch
+                    stateDescription = expandButtonStateDescription
+                },
+            onClick = { onExpandedChanged(!isExpanded) },
+            colors =
+                IconButtonDefaults.filledIconButtonColors(
+                    containerColor = sliderColors.indicatorColor,
+                    contentColor = sliderColors.iconColor
                 ),
-            contentDescription = null,
-        )
+        ) {
+            Icon(
+                painter =
+                    painterResource(
+                        if (isExpanded) {
+                            R.drawable.ic_filled_arrow_down
+                        } else {
+                            R.drawable.ic_filled_arrow_up
+                        }
+                    ),
+                contentDescription = null,
+            )
+        }
     }
 }
 
@@ -204,3 +222,63 @@
         ) +
         fadeOut(animationSpec = tween(durationMillis = exitDuration))
 }
+
+private fun expandButtonEnterTransition(): EnterTransition {
+    return fadeIn(
+        tween(
+            delayMillis = COLLAPSE_EXPAND_BUTTON_DELAY_MILLIS,
+            durationMillis = EXPAND_BUTTON_ANIMATION_DURATION_MILLIS,
+        )
+    ) +
+        scaleIn(
+            animationSpec =
+                tween(
+                    delayMillis = COLLAPSE_EXPAND_BUTTON_DELAY_MILLIS,
+                    durationMillis = EXPAND_BUTTON_ANIMATION_DURATION_MILLIS,
+                ),
+            initialScale = EXPAND_BUTTON_SCALE,
+        )
+}
+
+private fun expandButtonExitTransition(): ExitTransition {
+    return fadeOut(
+        tween(
+            delayMillis = EXPAND_DURATION_MILLIS,
+            durationMillis = EXPAND_BUTTON_ANIMATION_DURATION_MILLIS,
+        )
+    ) +
+        scaleOut(
+            animationSpec =
+                tween(
+                    delayMillis = EXPAND_DURATION_MILLIS,
+                    durationMillis = EXPAND_BUTTON_ANIMATION_DURATION_MILLIS,
+                ),
+            targetScale = EXPAND_BUTTON_SCALE,
+        )
+}
+
+@Composable
+private fun topSliderPadding(isExpandable: Boolean): State<Dp> {
+    val animationSpec: AnimationSpec<Dp> =
+        if (isExpandable) {
+            tween(
+                delayMillis = COLLAPSE_DURATION_MILLIS,
+                durationMillis = TOP_SLIDER_ANIMATION_DURATION_MILLIS,
+            )
+        } else {
+            tween(
+                delayMillis = EXPAND_DURATION_MILLIS,
+                durationMillis = TOP_SLIDER_ANIMATION_DURATION_MILLIS,
+            )
+        }
+    return animateDpAsState(
+        targetValue =
+            if (isExpandable) {
+                72.dp
+            } else {
+                0.dp
+            },
+        animationSpec = animationSpec,
+        label = "TopVolumeSliderPadding"
+    )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
index bb17499..d15430f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
@@ -18,9 +18,9 @@
 
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.PlatformSliderColors
 import com.android.compose.grid.VerticalGrid
 import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
@@ -39,7 +39,7 @@
         horizontalSpacing = 24.dp,
     ) {
         for (sliderViewModel in viewModels) {
-            val sliderState = sliderViewModel.slider.collectAsState().value
+            val sliderState = sliderViewModel.slider.collectAsStateWithLifecycle().value
             VolumeSlider(
                 modifier = Modifier.fillMaxWidth(),
                 state = sliderState,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index a46f4e5..271eb96 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -34,12 +34,15 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.CustomAccessibilityAction
 import androidx.compose.ui.semantics.ProgressBarRangeInfo
 import androidx.compose.ui.semantics.clearAndSetSemantics
 import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.customActions
 import androidx.compose.ui.semantics.disabled
 import androidx.compose.ui.semantics.progressBarRangeInfo
 import androidx.compose.ui.semantics.setProgress
+import androidx.compose.ui.semantics.stateDescription
 import androidx.compose.ui.unit.dp
 import com.android.compose.PlatformSlider
 import com.android.compose.PlatformSliderColors
@@ -60,14 +63,26 @@
     PlatformSlider(
         modifier =
             modifier.clearAndSetSemantics {
-                if (!state.isEnabled) disabled()
-                contentDescription =
-                    state.disabledMessage?.let { "${state.label}, $it" } ?: state.label
-
-                // provide a not animated value to the a11y because it fails to announce the
-                // settled value when it changes rapidly.
                 if (state.isEnabled) {
+                    contentDescription = state.label
+                    state.a11yClickDescription?.let {
+                        customActions =
+                            listOf(
+                                CustomAccessibilityAction(
+                                    it,
+                                ) {
+                                    onIconTapped()
+                                    true
+                                }
+                            )
+                    }
+
+                    state.a11yStateDescription?.let { stateDescription = it }
                     progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange)
+                } else {
+                    disabled()
+                    contentDescription =
+                        state.disabledMessage?.let { "${state.label}, $it" } ?: state.label
                 }
                 setProgress { targetValue ->
                     val targetDirection =
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
index fdf8ee8..770c5d5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
@@ -18,12 +18,13 @@
 
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.PlatformSliderDefaults
 import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
 import com.android.systemui.volume.panel.component.volume.ui.viewmodel.AudioVolumeComponentViewModel
+import com.android.systemui.volume.panel.component.volume.ui.viewmodel.SlidersExpandableViewModel
 import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
 import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
 import com.android.systemui.volume.panel.ui.composable.isPortrait
@@ -37,7 +38,8 @@
 
     @Composable
     override fun VolumePanelComposeScope.Content(modifier: Modifier) {
-        val sliderViewModels: List<SliderViewModel> by viewModel.sliderViewModels.collectAsState()
+        val sliderViewModels: List<SliderViewModel> by
+            viewModel.sliderViewModels.collectAsStateWithLifecycle()
         if (sliderViewModels.isEmpty()) {
             return
         }
@@ -48,13 +50,21 @@
                 modifier = modifier.fillMaxWidth(),
             )
         } else {
-            val isExpanded by viewModel.isExpanded.collectAsState()
+            val expandableViewModel: SlidersExpandableViewModel by
+                viewModel
+                    .isExpandable(isPortrait)
+                    .collectAsStateWithLifecycle(SlidersExpandableViewModel.Unavailable)
+            if (expandableViewModel is SlidersExpandableViewModel.Unavailable) {
+                return
+            }
+            val isExpanded =
+                (expandableViewModel as? SlidersExpandableViewModel.Expandable)?.isExpanded ?: true
             ColumnVolumeSliders(
                 viewModels = sliderViewModels,
                 isExpanded = isExpanded,
                 onExpandedChanged = viewModel::onExpandedChanged,
                 sliderColors = PlatformSliderDefaults.defaultPlatformSliderColors(),
-                isExpandable = isPortrait,
+                isExpandable = expandableViewModel is SlidersExpandableViewModel.Expandable,
                 modifier = modifier.fillMaxWidth(),
             )
         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
index c51e8b0..83b8158 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
@@ -24,11 +24,15 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.paneTitle
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.res.R
 import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
@@ -49,19 +53,22 @@
         }
     }
 
-    val state: VolumePanelState by viewModel.volumePanelState.collectAsState()
-    val components by viewModel.componentsLayout.collectAsState(null)
+    val accessibilityTitle = stringResource(R.string.accessibility_volume_settings)
+    val state: VolumePanelState by viewModel.volumePanelState.collectAsStateWithLifecycle()
+    val components by viewModel.componentsLayout.collectAsStateWithLifecycle(null)
 
     with(VolumePanelComposeScope(state)) {
         components?.let { componentsState ->
             Components(
                 componentsState,
-                modifier.padding(
-                    start = padding,
-                    top = padding,
-                    end = padding,
-                    bottom = 20.dp,
-                )
+                modifier
+                    .semantics { paneTitle = accessibilityTitle }
+                    .padding(
+                        start = padding,
+                        top = padding,
+                        end = padding,
+                        bottom = 20.dp,
+                    )
             )
         }
     }
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 cb4d572..6758990 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
@@ -877,6 +877,7 @@
     private val orientation: Orientation,
     private val topOrLeftBehavior: NestedScrollBehavior,
     private val bottomOrRightBehavior: NestedScrollBehavior,
+    private val isExternalOverscrollGesture: () -> Boolean,
 ) {
     private val layoutState = layoutImpl.state
     private val draggableHandler = layoutImpl.draggableHandler(orientation)
@@ -932,7 +933,8 @@
         return PriorityNestedScrollConnection(
             orientation = orientation,
             canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
-                canChangeScene = offsetBeforeStart == 0f
+                canChangeScene =
+                    if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f
 
                 val canInterceptSwipeTransition =
                     canChangeScene &&
@@ -962,7 +964,8 @@
                         else -> return@PriorityNestedScrollConnection false
                     }
 
-                val isZeroOffset = offsetBeforeStart == 0f
+                val isZeroOffset =
+                    if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f
 
                 val canStart =
                     when (behavior) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
index 5a2f85a..1fa6b3f7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
@@ -75,6 +75,7 @@
     orientation: Orientation,
     topOrLeftBehavior: NestedScrollBehavior,
     bottomOrRightBehavior: NestedScrollBehavior,
+    isExternalOverscrollGesture: () -> Boolean,
 ) =
     this then
         NestedScrollToSceneElement(
@@ -82,6 +83,7 @@
             orientation = orientation,
             topOrLeftBehavior = topOrLeftBehavior,
             bottomOrRightBehavior = bottomOrRightBehavior,
+            isExternalOverscrollGesture = isExternalOverscrollGesture,
         )
 
 private data class NestedScrollToSceneElement(
@@ -89,6 +91,7 @@
     private val orientation: Orientation,
     private val topOrLeftBehavior: NestedScrollBehavior,
     private val bottomOrRightBehavior: NestedScrollBehavior,
+    private val isExternalOverscrollGesture: () -> Boolean,
 ) : ModifierNodeElement<NestedScrollToSceneNode>() {
     override fun create() =
         NestedScrollToSceneNode(
@@ -96,6 +99,7 @@
             orientation = orientation,
             topOrLeftBehavior = topOrLeftBehavior,
             bottomOrRightBehavior = bottomOrRightBehavior,
+            isExternalOverscrollGesture = isExternalOverscrollGesture,
         )
 
     override fun update(node: NestedScrollToSceneNode) {
@@ -104,6 +108,7 @@
             orientation = orientation,
             topOrLeftBehavior = topOrLeftBehavior,
             bottomOrRightBehavior = bottomOrRightBehavior,
+            isExternalOverscrollGesture = isExternalOverscrollGesture,
         )
     }
 
@@ -121,6 +126,7 @@
     orientation: Orientation,
     topOrLeftBehavior: NestedScrollBehavior,
     bottomOrRightBehavior: NestedScrollBehavior,
+    isExternalOverscrollGesture: () -> Boolean,
 ) : DelegatingNode() {
     private var priorityNestedScrollConnection: PriorityNestedScrollConnection =
         scenePriorityNestedScrollConnection(
@@ -128,6 +134,7 @@
             orientation = orientation,
             topOrLeftBehavior = topOrLeftBehavior,
             bottomOrRightBehavior = bottomOrRightBehavior,
+            isExternalOverscrollGesture = isExternalOverscrollGesture,
         )
 
     private var nestedScrollNode: DelegatableNode =
@@ -150,6 +157,7 @@
         orientation: Orientation,
         topOrLeftBehavior: NestedScrollBehavior,
         bottomOrRightBehavior: NestedScrollBehavior,
+        isExternalOverscrollGesture: () -> Boolean,
     ) {
         // Clean up the old nested scroll connection
         priorityNestedScrollConnection.reset()
@@ -162,6 +170,7 @@
                 orientation = orientation,
                 topOrLeftBehavior = topOrLeftBehavior,
                 bottomOrRightBehavior = bottomOrRightBehavior,
+                isExternalOverscrollGesture = isExternalOverscrollGesture,
             )
         nestedScrollNode =
             nestedScrollModifierNode(
@@ -177,11 +186,13 @@
     orientation: Orientation,
     topOrLeftBehavior: NestedScrollBehavior,
     bottomOrRightBehavior: NestedScrollBehavior,
+    isExternalOverscrollGesture: () -> Boolean,
 ) =
     NestedScrollHandlerImpl(
             layoutImpl = layoutImpl,
             orientation = orientation,
             topOrLeftBehavior = topOrLeftBehavior,
             bottomOrRightBehavior = bottomOrRightBehavior,
+            isExternalOverscrollGesture = isExternalOverscrollGesture,
         )
         .connection
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
index d924d88..92d5c26 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
@@ -74,6 +74,16 @@
          */
         val isUserInputOngoing: Flow<Boolean>,
     ) : ObservableTransitionState
+
+    fun isIdle(scene: SceneKey?): Boolean {
+        return this is Idle && (scene == null || this.currentScene == scene)
+    }
+
+    fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean {
+        return this is Transition &&
+            (from == null || this.fromScene == from) &&
+            (to == null || this.toScene == to)
+    }
 }
 
 /**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 339868c..6fef33c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -141,23 +141,27 @@
     override fun Modifier.horizontalNestedScrollToScene(
         leftBehavior: NestedScrollBehavior,
         rightBehavior: NestedScrollBehavior,
+        isExternalOverscrollGesture: () -> Boolean,
     ): Modifier =
         nestedScrollToScene(
             layoutImpl = layoutImpl,
             orientation = Orientation.Horizontal,
             topOrLeftBehavior = leftBehavior,
             bottomOrRightBehavior = rightBehavior,
+            isExternalOverscrollGesture = isExternalOverscrollGesture,
         )
 
     override fun Modifier.verticalNestedScrollToScene(
         topBehavior: NestedScrollBehavior,
-        bottomBehavior: NestedScrollBehavior
+        bottomBehavior: NestedScrollBehavior,
+        isExternalOverscrollGesture: () -> Boolean,
     ): Modifier =
         nestedScrollToScene(
             layoutImpl = layoutImpl,
             orientation = Orientation.Vertical,
             topOrLeftBehavior = topBehavior,
             bottomOrRightBehavior = bottomBehavior,
+            isExternalOverscrollGesture = isExternalOverscrollGesture,
         )
 
     override fun Modifier.noResizeDuringTransitions(): Modifier {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index c7c874c..11e711a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -250,6 +250,7 @@
     fun Modifier.horizontalNestedScrollToScene(
         leftBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
         rightBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
+        isExternalOverscrollGesture: () -> Boolean = { false },
     ): Modifier
 
     /**
@@ -262,6 +263,7 @@
     fun Modifier.verticalNestedScrollToScene(
         topBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
         bottomBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
+        isExternalOverscrollGesture: () -> Boolean = { false },
     ): Modifier
 
     /**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 7f94f0d..4e3a032 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -117,6 +117,9 @@
         coroutineScope: CoroutineScope,
         transitionKey: TransitionKey? = null,
     ): TransitionState.Transition?
+
+    /** Immediately snap to the given [scene]. */
+    fun snapToScene(scene: SceneKey)
 }
 
 /**
@@ -745,6 +748,17 @@
     override fun CoroutineScope.onChangeScene(scene: SceneKey) {
         setTargetScene(scene, coroutineScope = this)
     }
+
+    override fun snapToScene(scene: SceneKey) {
+        // Force finish all transitions.
+        while (currentTransitions.isNotEmpty()) {
+            val transition = transitionStates[0] as TransitionState.Transition
+            finishTransition(transition, transition.currentScene)
+        }
+
+        check(transitionStates.size == 1)
+        transitionStates[0] = TransitionState.Idle(scene)
+    }
 }
 
 private const val TAG = "SceneTransitionLayoutState"
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index 1fd1bf4..8625482 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -32,12 +32,11 @@
 import com.android.compose.animation.scene.TestScenes.SceneA
 import com.android.compose.animation.scene.TestScenes.SceneB
 import com.android.compose.animation.scene.TestScenes.SceneC
-import com.android.compose.animation.scene.TransitionState.Idle
 import com.android.compose.animation.scene.TransitionState.Transition
+import com.android.compose.animation.scene.subjects.assertThat
 import com.android.compose.test.MonotonicClockTestScope
 import com.android.compose.test.runMonotonicClockTest
 import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.cancelAndJoin
 import kotlinx.coroutines.launch
@@ -103,12 +102,16 @@
         val draggableHandler = layoutImpl.draggableHandler(Orientation.Vertical)
         val horizontalDraggableHandler = layoutImpl.draggableHandler(Orientation.Horizontal)
 
-        fun nestedScrollConnection(nestedScrollBehavior: NestedScrollBehavior) =
+        fun nestedScrollConnection(
+            nestedScrollBehavior: NestedScrollBehavior,
+            isExternalOverscrollGesture: Boolean = false
+        ) =
             NestedScrollHandlerImpl(
                     layoutImpl = layoutImpl,
                     orientation = draggableHandler.orientation,
                     topOrLeftBehavior = nestedScrollBehavior,
                     bottomOrRightBehavior = nestedScrollBehavior,
+                    isExternalOverscrollGesture = { isExternalOverscrollGesture }
                 )
                 .connection
 
@@ -145,10 +148,8 @@
         }
 
         fun assertIdle(currentScene: SceneKey) {
-            assertThat(transitionState).isInstanceOf(Idle::class.java)
-            assertWithMessage("currentScene does not match")
-                .that(transitionState.currentScene)
-                .isEqualTo(currentScene)
+            assertThat(transitionState).isIdle()
+            assertThat(transitionState).hasCurrentScene(currentScene)
         }
 
         fun assertTransition(
@@ -158,34 +159,12 @@
             progress: Float? = null,
             isUserInputOngoing: Boolean? = null
         ) {
-            assertThat(transitionState).isInstanceOf(Transition::class.java)
-            val transition = transitionState as Transition
-
-            if (currentScene != null)
-                assertWithMessage("currentScene does not match")
-                    .that(transition.currentScene)
-                    .isEqualTo(currentScene)
-
-            if (fromScene != null)
-                assertWithMessage("fromScene does not match")
-                    .that(transition.fromScene)
-                    .isEqualTo(fromScene)
-
-            if (toScene != null)
-                assertWithMessage("toScene does not match")
-                    .that(transition.toScene)
-                    .isEqualTo(toScene)
-
-            if (progress != null)
-                assertWithMessage("progress does not match")
-                    .that(transition.progress)
-                    .isWithin(0f) // returns true when comparing 0.0f with -0.0f
-                    .of(progress)
-
-            if (isUserInputOngoing != null)
-                assertWithMessage("isUserInputOngoing does not match")
-                    .that(transition.isUserInputOngoing)
-                    .isEqualTo(isUserInputOngoing)
+            val transition = assertThat(transitionState).isTransition()
+            currentScene?.let { assertThat(transition).hasCurrentScene(it) }
+            fromScene?.let { assertThat(transition).hasFromScene(it) }
+            toScene?.let { assertThat(transition).hasToScene(it) }
+            progress?.let { assertThat(transition).hasProgress(it) }
+            isUserInputOngoing?.let { assertThat(transition).hasIsUserInputOngoing(it) }
         }
 
         fun onDragStarted(
@@ -801,6 +780,26 @@
     }
 
     @Test
+    fun flingAfterScrollStartedByExternalOverscrollGesture() = runGestureTest {
+        val nestedScroll =
+            nestedScrollConnection(
+                nestedScrollBehavior = EdgeWithPreview,
+                isExternalOverscrollGesture = true
+            )
+
+        // scroll not consumed in child
+        nestedScroll.scroll(
+            available = downOffset(fractionOfScreen = 0.1f),
+        )
+
+        // scroll offsetY10 is all available for parents
+        nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
+        assertTransition(SceneA)
+
+        nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
+    }
+
+    @Test
     fun beforeNestedScrollStart_stop_shouldBeIgnored() = runGestureTest {
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
         nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index bbf3d8a..e19dc96 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -20,7 +20,6 @@
 import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.rememberScrollableState
 import androidx.compose.foundation.gestures.scrollable
@@ -43,7 +42,6 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.layout.approachLayout
@@ -64,6 +62,7 @@
 import com.android.compose.animation.scene.TestScenes.SceneA
 import com.android.compose.animation.scene.TestScenes.SceneB
 import com.android.compose.animation.scene.TestScenes.SceneC
+import com.android.compose.animation.scene.subjects.assertThat
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
@@ -78,7 +77,6 @@
     @get:Rule val rule = createComposeRule()
 
     @Composable
-    @OptIn(ExperimentalComposeUiApi::class)
     private fun SceneScope.Element(
         key: ElementKey,
         size: Dp,
@@ -496,7 +494,6 @@
     }
 
     @Test
-    @OptIn(ExperimentalFoundationApi::class)
     fun elementModifierNodeIsRecycledInLazyLayouts() = runTest {
         val nPages = 2
         val pagerState = PagerState(currentPage = 0) { nPages }
@@ -654,8 +651,7 @@
             }
         }
 
-        assertThat(state.currentTransition).isNull()
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNull()
+        assertThat(state.transitionState).isIdle()
 
         // Swipe by half of verticalSwipeDistance.
         rule.onRoot().performTouchInput {
@@ -691,9 +687,9 @@
 
         val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
         fooElement.assertTopPositionInRootIsEqualTo(0.dp)
-        val transition = state.currentTransition
+        val transition = assertThat(state.transitionState).isTransition()
         assertThat(transition).isNotNull()
-        assertThat(transition!!.progress).isEqualTo(0.5f)
+        assertThat(transition).hasProgress(0.5f)
         assertThat(animatedFloat).isEqualTo(50f)
 
         rule.onRoot().performTouchInput {
@@ -702,8 +698,8 @@
         }
 
         // Scroll 150% (Scene B overscroll by 50%)
-        assertThat(transition.progress).isEqualTo(1.5f)
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull()
+        assertThat(transition).hasProgress(1.5f)
+        assertThat(transition).hasOverscrollSpec()
         fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 0.5f)
         // animatedFloat cannot overflow (canOverflow = false)
         assertThat(animatedFloat).isEqualTo(100f)
@@ -714,8 +710,8 @@
         }
 
         // Scroll 250% (Scene B overscroll by 150%)
-        assertThat(transition.progress).isEqualTo(2.5f)
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull()
+        assertThat(transition).hasProgress(2.5f)
+        assertThat(transition).hasOverscrollSpec()
         fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 1.5f)
         assertThat(animatedFloat).isEqualTo(100f)
     }
@@ -766,8 +762,7 @@
             }
         }
 
-        assertThat(state.currentTransition).isNull()
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNull()
+        assertThat(state.transitionState).isIdle()
         val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
         fooElement.assertTopPositionInRootIsEqualTo(0.dp)
 
@@ -779,10 +774,9 @@
             moveBy(Offset(0f, touchSlop + layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
         }
 
-        val transition = state.currentTransition
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull()
-        assertThat(transition).isNotNull()
-        assertThat(transition!!.progress).isEqualTo(-0.5f)
+        val transition = assertThat(state.transitionState).isTransition()
+        assertThat(transition).hasOverscrollSpec()
+        assertThat(transition).hasProgress(-0.5f)
         fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 0.5f)
 
         rule.onRoot().performTouchInput {
@@ -791,8 +785,8 @@
         }
 
         // Scroll 150% (Scene B overscroll by 50%)
-        assertThat(transition.progress).isEqualTo(-1.5f)
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull()
+        assertThat(transition).hasProgress(-1.5f)
+        assertThat(transition).hasOverscrollSpec()
         fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 1.5f)
     }
 
@@ -825,13 +819,12 @@
             moveBy(Offset(0f, layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
         }
 
-        val transition = state.currentTransition
-        assertThat(transition).isNotNull()
+        val transition = assertThat(state.transitionState).isTransition()
         assertThat(animatedFloat).isEqualTo(100f)
 
         // Scroll 150% (100% scroll + 50% overscroll)
-        assertThat(transition!!.progress).isEqualTo(1.5f)
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull()
+        assertThat(transition).hasProgress(1.5f)
+        assertThat(transition).hasOverscrollSpec()
         fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 0.5f)
         assertThat(animatedFloat).isEqualTo(100f)
 
@@ -841,8 +834,8 @@
         }
 
         // Scroll 250% (100% scroll + 150% overscroll)
-        assertThat(transition.progress).isEqualTo(2.5f)
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull()
+        assertThat(transition).hasProgress(2.5f)
+        assertThat(transition).hasOverscrollSpec()
         fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 1.5f)
         assertThat(animatedFloat).isEqualTo(100f)
     }
@@ -882,13 +875,11 @@
             moveBy(Offset(0f, layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
         }
 
-        val transition = state.currentTransition
-        assertThat(transition).isNotNull()
-        transition as TransitionState.HasOverscrollProperties
+        val transition = assertThat(state.transitionState).isTransition()
 
         // Scroll 150% (100% scroll + 50% overscroll)
-        assertThat(transition.progress).isEqualTo(1.5f)
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull()
+        assertThat(transition).hasProgress(1.5f)
+        assertThat(transition).hasOverscrollSpec()
         fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * (transition.progress - 1f))
         assertThat(animatedFloat).isEqualTo(100f)
 
@@ -900,8 +891,8 @@
         rule.waitUntil(timeoutMillis = 10_000) { transition.progress < 1f }
 
         assertThat(transition.progress).isLessThan(1f)
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull()
-        assertThat(transition.bouncingScene).isEqualTo(transition.toScene)
+        assertThat(transition).hasOverscrollSpec()
+        assertThat(transition).hasBouncingScene(transition.toScene)
         assertThat(animatedFloat).isEqualTo(100f)
     }
 
@@ -980,13 +971,13 @@
 
         val transitions = state.currentTransitions
         assertThat(transitions).hasSize(2)
-        assertThat(transitions[0].fromScene).isEqualTo(SceneA)
-        assertThat(transitions[0].toScene).isEqualTo(SceneB)
-        assertThat(transitions[0].progress).isEqualTo(0f)
+        assertThat(transitions[0]).hasFromScene(SceneA)
+        assertThat(transitions[0]).hasToScene(SceneB)
+        assertThat(transitions[0]).hasProgress(0f)
 
-        assertThat(transitions[1].fromScene).isEqualTo(SceneB)
-        assertThat(transitions[1].toScene).isEqualTo(SceneC)
-        assertThat(transitions[1].progress).isEqualTo(0f)
+        assertThat(transitions[1]).hasFromScene(SceneB)
+        assertThat(transitions[1]).hasToScene(SceneC)
+        assertThat(transitions[1]).hasProgress(0f)
 
         // First frame: both are at x = 0dp. For the whole transition, Foo is at y = 0dp and Bar is
         // at y = layoutSize - elementSoze = 100dp.
@@ -1153,7 +1144,7 @@
         state.finishTransition(aToB, SceneB)
         state.finishTransition(bToC, SceneC)
         rule.waitForIdle()
-        assertThat(state.currentTransition).isNull()
+        assertThat(state.transitionState).isIdle()
 
         // The interruption values should be unspecified and deltas should be set to zero.
         val foo = layoutImpl.elements.getValue(TestElements.Foo)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
index ba9cf7f..85d4165 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
@@ -22,6 +22,7 @@
 import com.android.compose.animation.scene.TestScenes.SceneA
 import com.android.compose.animation.scene.TestScenes.SceneB
 import com.android.compose.animation.scene.TestScenes.SceneC
+import com.android.compose.animation.scene.subjects.assertThat
 import com.android.compose.test.runMonotonicClockTest
 import com.google.common.truth.Correspondence
 import com.google.common.truth.Truth.assertThat
@@ -165,10 +166,10 @@
         // pair, and its velocity is used when animating the progress back to 0.
         val bToA = checkNotNull(state.setTargetScene(SceneA, coroutineScope = this))
         testScheduler.runCurrent()
-        assertThat(bToA.fromScene).isEqualTo(SceneA)
-        assertThat(bToA.toScene).isEqualTo(SceneB)
-        assertThat(bToA.currentScene).isEqualTo(SceneA)
-        assertThat(bToA.progressVelocity).isEqualTo(progressVelocity)
+        assertThat(bToA).hasFromScene(SceneA)
+        assertThat(bToA).hasToScene(SceneB)
+        assertThat(bToA).hasCurrentScene(SceneA)
+        assertThat(bToA).hasProgressVelocity(progressVelocity)
     }
 
     @Test
@@ -191,10 +192,10 @@
         // and its velocity is used when animating the progress to 1.
         val bToA = checkNotNull(state.setTargetScene(SceneB, coroutineScope = this))
         testScheduler.runCurrent()
-        assertThat(bToA.fromScene).isEqualTo(SceneA)
-        assertThat(bToA.toScene).isEqualTo(SceneB)
-        assertThat(bToA.currentScene).isEqualTo(SceneB)
-        assertThat(bToA.progressVelocity).isEqualTo(progressVelocity)
+        assertThat(bToA).hasFromScene(SceneA)
+        assertThat(bToA).hasToScene(SceneB)
+        assertThat(bToA).hasCurrentScene(SceneB)
+        assertThat(bToA).hasProgressVelocity(progressVelocity)
     }
 
     companion object {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index 224ffe2..9523896 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -43,6 +43,7 @@
 import androidx.compose.ui.test.performClick
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.subjects.assertThat
 import com.android.compose.test.assertSizeIsEqualTo
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
@@ -157,8 +158,8 @@
                             fromSceneZIndex: Float,
                             toSceneZIndex: Float
                         ): SceneKey {
-                            assertThat(transition.fromScene).isEqualTo(TestScenes.SceneA)
-                            assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+                            assertThat(transition).hasFromScene(TestScenes.SceneA)
+                            assertThat(transition).hasToScene(TestScenes.SceneB)
                             assertThat(fromSceneZIndex).isEqualTo(0)
                             assertThat(toSceneZIndex).isEqualTo(1)
 
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 93e94f8..d2c8bd6 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -25,6 +25,7 @@
 import com.android.compose.animation.scene.TestScenes.SceneB
 import com.android.compose.animation.scene.TestScenes.SceneC
 import com.android.compose.animation.scene.TestScenes.SceneD
+import com.android.compose.animation.scene.subjects.assertThat
 import com.android.compose.animation.scene.transition.link.StateLink
 import com.android.compose.test.runMonotonicClockTest
 import com.google.common.truth.Truth.assertThat
@@ -322,8 +323,8 @@
         // Go back to A.
         state.setTargetScene(SceneA, coroutineScope = this)
         testScheduler.advanceUntilIdle()
-        assertThat(state.currentTransition).isNull()
-        assertThat(state.transitionState.currentScene).isEqualTo(SceneA)
+        assertThat(state.transitionState).isIdle()
+        assertThat(state.transitionState).hasCurrentScene(SceneA)
 
         // Specific transition from A to B.
         assertThat(
@@ -477,23 +478,24 @@
                         overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) }
                     }
             )
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNull()
+        val transition = assertThat(state.transitionState).isTransition()
+        assertThat(transition).hasNoOverscrollSpec()
 
         // overscroll for SceneA is NOT defined
         progress.value = -0.1f
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNull()
+        assertThat(transition).hasNoOverscrollSpec()
 
         // scroll from SceneA to SceneB
         progress.value = 0.5f
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNull()
+        assertThat(transition).hasNoOverscrollSpec()
 
         progress.value = 1f
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNull()
+        assertThat(transition).hasNoOverscrollSpec()
 
         // overscroll for SceneB is defined
         progress.value = 1.1f
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull()
-        assertThat(state.currentTransition?.currentOverscrollSpec?.scene).isEqualTo(SceneB)
+        val overscrollSpec = assertThat(transition).hasOverscrollSpec()
+        assertThat(overscrollSpec.scene).isEqualTo(SceneB)
     }
 
     @Test
@@ -507,23 +509,25 @@
                         overscroll(SceneA, Orientation.Vertical) { fade(TestElements.Foo) }
                     }
             )
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNull()
+
+        val transition = assertThat(state.transitionState).isTransition()
+        assertThat(transition).hasNoOverscrollSpec()
 
         // overscroll for SceneA is defined
         progress.value = -0.1f
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull()
-        assertThat(state.currentTransition?.currentOverscrollSpec?.scene).isEqualTo(SceneA)
+        val overscrollSpec = assertThat(transition).hasOverscrollSpec()
+        assertThat(overscrollSpec.scene).isEqualTo(SceneA)
 
         // scroll from SceneA to SceneB
         progress.value = 0.5f
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNull()
+        assertThat(transition).hasNoOverscrollSpec()
 
         progress.value = 1f
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNull()
+        assertThat(transition).hasNoOverscrollSpec()
 
         // overscroll for SceneB is NOT defined
         progress.value = 1.1f
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNull()
+        assertThat(transition).hasNoOverscrollSpec()
     }
 
     @Test
@@ -534,22 +538,24 @@
                 progress = { progress.value },
                 sceneTransitions = transitions {}
             )
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNull()
+
+        val transition = assertThat(state.transitionState).isTransition()
+        assertThat(transition).hasNoOverscrollSpec()
 
         // overscroll for SceneA is NOT defined
         progress.value = -0.1f
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNull()
+        assertThat(transition).hasNoOverscrollSpec()
 
         // scroll from SceneA to SceneB
         progress.value = 0.5f
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNull()
+        assertThat(transition).hasNoOverscrollSpec()
 
         progress.value = 1f
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNull()
+        assertThat(transition).hasNoOverscrollSpec()
 
         // overscroll for SceneB is NOT defined
         progress.value = 1.1f
-        assertThat(state.currentTransition?.currentOverscrollSpec).isNull()
+        assertThat(transition).hasNoOverscrollSpec()
     }
 
     @Test
@@ -629,4 +635,19 @@
             Log.setWtfHandler(originalHandler)
         }
     }
+
+    @Test
+    fun snapToScene() = runMonotonicClockTest {
+        val state = MutableSceneTransitionLayoutState(SceneA)
+
+        // Transition to B.
+        state.setTargetScene(SceneB, coroutineScope = this)
+        val transition = assertThat(state.transitionState).isTransition()
+        assertThat(transition).hasCurrentScene(SceneB)
+
+        // Snap to C.
+        state.snapToScene(SceneC)
+        assertThat(state.transitionState).isIdle()
+        assertThat(state.transitionState).hasCurrentScene(SceneC)
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 7836581..692c18b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -51,6 +51,7 @@
 import com.android.compose.animation.scene.TestScenes.SceneA
 import com.android.compose.animation.scene.TestScenes.SceneB
 import com.android.compose.animation.scene.TestScenes.SceneC
+import com.android.compose.animation.scene.subjects.assertThat
 import com.android.compose.test.assertSizeIsEqualTo
 import com.android.compose.test.subjects.DpOffsetSubject
 import com.android.compose.test.subjects.assertThat
@@ -147,34 +148,34 @@
         rule.onNodeWithText("SceneA").assertIsDisplayed()
         rule.onNodeWithText("SceneB").assertDoesNotExist()
         rule.onNodeWithText("SceneC").assertDoesNotExist()
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
 
         // Change to scene B. Only that scene is displayed.
         currentScene = SceneB
         rule.onNodeWithText("SceneA").assertDoesNotExist()
         rule.onNodeWithText("SceneB").assertIsDisplayed()
         rule.onNodeWithText("SceneC").assertDoesNotExist()
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB)
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneB)
     }
 
     @Test
     fun testBack() {
         rule.setContent { TestContent() }
 
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
 
         rule.activity.onBackPressed()
         rule.waitForIdle()
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB)
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneB)
     }
 
     @Test
     fun testTransitionState() {
         rule.setContent { TestContent() }
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
 
         // We will advance the clock manually.
         rule.mainClock.autoAdvance = false
@@ -182,45 +183,38 @@
         // Change the current scene. Until composition is triggered, this won't change the layout
         // state.
         currentScene = SceneB
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
 
         // On the next frame, we will recompose because currentScene changed, which will start the
         // transition (i.e. it will change the transitionState to be a Transition) in a
         // LaunchedEffect.
         rule.mainClock.advanceTimeByFrame()
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java)
-        val transition = layoutState.transitionState as TransitionState.Transition
-        assertThat(transition.fromScene).isEqualTo(SceneA)
-        assertThat(transition.toScene).isEqualTo(SceneB)
-        assertThat(transition.progress).isEqualTo(0f)
+        val transition = assertThat(layoutState.transitionState).isTransition()
+        assertThat(transition).hasFromScene(SceneA)
+        assertThat(transition).hasToScene(SceneB)
+        assertThat(transition).hasProgress(0f)
 
         // Then, on the next frame, the animator we started gets its initial value and clock
         // starting time. We are now at progress = 0f.
         rule.mainClock.advanceTimeByFrame()
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java)
-        assertThat((layoutState.transitionState as TransitionState.Transition).progress)
-            .isEqualTo(0f)
+        assertThat(transition).hasProgress(0f)
 
         // The test transition lasts 480ms. 240ms after the start of the transition, we are at
         // progress = 0.5f.
         rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java)
-        assertThat((layoutState.transitionState as TransitionState.Transition).progress)
-            .isEqualTo(0.5f)
+        assertThat(transition).hasProgress(0.5f)
 
         // (240-16) ms later, i.e. one frame before the transition is finished, we are at
         // progress=(480-16)/480.
         rule.mainClock.advanceTimeBy(TestTransitionDuration / 2 - 16)
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java)
-        assertThat((layoutState.transitionState as TransitionState.Transition).progress)
-            .isEqualTo((TestTransitionDuration - 16) / 480f)
+        assertThat(transition).hasProgress((TestTransitionDuration - 16) / 480f)
 
         // one frame (16ms) later, the transition is finished and we are in the idle state in scene
         // B.
         rule.mainClock.advanceTimeByFrame()
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB)
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneB)
     }
 
     @Test
@@ -261,8 +255,8 @@
         // 100.dp. We pause at the middle of the transition, so it should now be 75.dp given that we
         // use a linear interpolator. Foo was at (x = layoutSize - 50dp, y = 0) in SceneA and is
         // going to (x = 0, y = 0), so the offset should now be half what it was.
-        assertThat((layoutState.transitionState as TransitionState.Transition).progress)
-            .isEqualTo(0.5f)
+        var transition = assertThat(layoutState.transitionState).isTransition()
+        assertThat(transition).hasProgress(0.5f)
         sharedFoo.assertWidthIsEqualTo(75.dp)
         sharedFoo.assertHeightIsEqualTo(75.dp)
         sharedFoo.assertPositionInRootIsEqualTo(
@@ -290,8 +284,8 @@
         val expectedSize = 100.dp + (150.dp - 100.dp) * interpolatedProgress
 
         sharedFoo = rule.onNode(isElement(TestElements.Foo, SceneC))
-        assertThat((layoutState.transitionState as TransitionState.Transition).progress)
-            .isEqualTo(interpolatedProgress)
+        transition = assertThat(layoutState.transitionState).isTransition()
+        assertThat(transition).hasProgress(interpolatedProgress)
         sharedFoo.assertWidthIsEqualTo(expectedSize)
         sharedFoo.assertHeightIsEqualTo(expectedSize)
         sharedFoo.assertPositionInRootIsEqualTo(expectedLeft, expectedTop)
@@ -305,16 +299,16 @@
 
         // Wait for the transition to C to finish.
         rule.mainClock.advanceTimeBy(TestTransitionDuration)
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneC)
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneC)
 
         // Go back to scene A. This should happen instantly (once the animation started, i.e. after
         // 2 frames) given that we use a snap() animation spec.
         currentScene = SceneA
         rule.mainClock.advanceTimeByFrame()
         rule.mainClock.advanceTimeByFrame()
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
     }
 
     @Test
@@ -384,7 +378,9 @@
         rule.mainClock.advanceTimeByFrame()
         rule.mainClock.advanceTimeBy(duration / 2)
         rule.waitForIdle()
-        assertThat(state.currentTransition?.progress).isEqualTo(0.5f)
+
+        var transition = assertThat(state.transitionState).isTransition()
+        assertThat(transition).hasProgress(0.5f)
 
         // A and B are composed.
         rule.onNodeWithTag("aRoot").assertExists()
@@ -396,7 +392,9 @@
         rule.mainClock.advanceTimeByFrame()
         rule.mainClock.advanceTimeByFrame()
         rule.waitForIdle()
-        assertThat(state.currentTransition?.progress).isEqualTo(0f)
+
+        transition = assertThat(state.transitionState).isTransition()
+        assertThat(transition).hasProgress(0f)
 
         // A, B and C are composed.
         rule.onNodeWithTag("aRoot").assertExists()
@@ -405,7 +403,7 @@
 
         // Let A => B finish.
         rule.mainClock.advanceTimeBy(duration / 2L)
-        assertThat(state.currentTransition?.progress).isEqualTo(0.5f)
+        assertThat(transition).hasProgress(0.5f)
         rule.waitForIdle()
 
         // B and C are composed.
@@ -416,8 +414,8 @@
         // Let B => C finish.
         rule.mainClock.advanceTimeBy(duration / 2L)
         rule.mainClock.advanceTimeByFrame()
-        assertThat(state.currentTransition).isNull()
         rule.waitForIdle()
+        assertThat(state.transitionState).isIdle()
 
         // Only C is composed.
         rule.onNodeWithTag("aRoot").assertDoesNotExist()
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index f034c18..1dd9322 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -38,6 +38,9 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.subjects.assertThat
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
@@ -65,7 +68,7 @@
     @get:Rule val rule = createComposeRule()
 
     private fun layoutState(
-        initialScene: SceneKey = TestScenes.SceneA,
+        initialScene: SceneKey = SceneA,
         transitions: SceneTransitions = EmptyTestTransitions,
     ) = MutableSceneTransitionLayoutState(initialScene, transitions)
 
@@ -80,22 +83,21 @@
             modifier = Modifier.size(LayoutWidth, LayoutHeight).testTag(TestElements.Foo.debugName),
         ) {
             scene(
-                TestScenes.SceneA,
+                SceneA,
                 userActions =
                     if (swipesEnabled())
                         mapOf(
-                            Swipe.Left to TestScenes.SceneB,
+                            Swipe.Left to SceneB,
                             Swipe.Down to TestScenes.SceneC,
-                            Swipe.Up to TestScenes.SceneB,
+                            Swipe.Up to SceneB,
                         )
                     else emptyMap(),
             ) {
                 Box(Modifier.fillMaxSize())
             }
             scene(
-                TestScenes.SceneB,
-                userActions =
-                    if (swipesEnabled()) mapOf(Swipe.Right to TestScenes.SceneA) else emptyMap(),
+                SceneB,
+                userActions = if (swipesEnabled()) mapOf(Swipe.Right to SceneA) else emptyMap(),
             ) {
                 Box(Modifier.fillMaxSize())
             }
@@ -104,11 +106,10 @@
                 userActions =
                     if (swipesEnabled())
                         mapOf(
-                            Swipe.Down to TestScenes.SceneA,
-                            Swipe(SwipeDirection.Down, pointerCount = 2) to TestScenes.SceneB,
-                            Swipe(SwipeDirection.Right, fromSource = Edge.Left) to
-                                TestScenes.SceneB,
-                            Swipe(SwipeDirection.Down, fromSource = Edge.Top) to TestScenes.SceneB,
+                            Swipe.Down to SceneA,
+                            Swipe(SwipeDirection.Down, pointerCount = 2) to SceneB,
+                            Swipe(SwipeDirection.Right, fromSource = Edge.Left) to SceneB,
+                            Swipe(SwipeDirection.Down, fromSource = Edge.Top) to SceneB,
                         )
                     else emptyMap(),
             ) {
@@ -129,8 +130,8 @@
             TestContent(layoutState)
         }
 
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
 
         // Drag left (i.e. from right to left) by 55dp. We pick 55dp here because 56dp is the
         // positional threshold from which we commit the gesture.
@@ -144,31 +145,27 @@
 
         // We should be at a progress = 55dp / LayoutWidth given that we use the layout size in
         // the gesture axis as swipe distance.
-        var transition = layoutState.transitionState
-        assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
-        assertThat((transition as TransitionState.Transition).fromScene)
-            .isEqualTo(TestScenes.SceneA)
-        assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
-        assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA)
-        assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth)
-        assertThat(transition.isInitiatedByUserInput).isTrue()
+        var transition = assertThat(layoutState.transitionState).isTransition()
+        assertThat(transition).hasFromScene(SceneA)
+        assertThat(transition).hasToScene(SceneB)
+        assertThat(transition).hasCurrentScene(SceneA)
+        assertThat(transition).hasProgress(55.dp / LayoutWidth)
+        assertThat(transition).isInitiatedByUserInput()
 
         // Release the finger. We should now be animating back to A (currentScene = SceneA) given
         // that 55dp < positional threshold.
         rule.onRoot().performTouchInput { up() }
-        transition = layoutState.transitionState
-        assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
-        assertThat((transition as TransitionState.Transition).fromScene)
-            .isEqualTo(TestScenes.SceneA)
-        assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
-        assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA)
-        assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth)
-        assertThat(transition.isInitiatedByUserInput).isTrue()
+        transition = assertThat(layoutState.transitionState).isTransition()
+        assertThat(transition).hasFromScene(SceneA)
+        assertThat(transition).hasToScene(SceneB)
+        assertThat(transition).hasCurrentScene(SceneA)
+        assertThat(transition).hasProgress(55.dp / LayoutWidth)
+        assertThat(transition).isInitiatedByUserInput()
 
         // Wait for the animation to finish. We should now be in scene A.
         rule.waitForIdle()
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
 
         // Now we do the same but vertically and with a drag distance of 56dp, which is >=
         // positional threshold.
@@ -178,31 +175,27 @@
         }
 
         // Drag is in progress, so currentScene = SceneA and progress = 56dp / LayoutHeight
-        transition = layoutState.transitionState
-        assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
-        assertThat((transition as TransitionState.Transition).fromScene)
-            .isEqualTo(TestScenes.SceneA)
-        assertThat(transition.toScene).isEqualTo(TestScenes.SceneC)
-        assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA)
-        assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight)
-        assertThat(transition.isInitiatedByUserInput).isTrue()
+        transition = assertThat(layoutState.transitionState).isTransition()
+        assertThat(transition).hasFromScene(SceneA)
+        assertThat(transition).hasToScene(TestScenes.SceneC)
+        assertThat(transition).hasCurrentScene(SceneA)
+        assertThat(transition).hasProgress(56.dp / LayoutHeight)
+        assertThat(transition).isInitiatedByUserInput()
 
         // Release the finger. We should now be animating to C (currentScene = SceneC) given
         // that 56dp >= positional threshold.
         rule.onRoot().performTouchInput { up() }
-        transition = layoutState.transitionState
-        assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
-        assertThat((transition as TransitionState.Transition).fromScene)
-            .isEqualTo(TestScenes.SceneA)
-        assertThat(transition.toScene).isEqualTo(TestScenes.SceneC)
-        assertThat(transition.currentScene).isEqualTo(TestScenes.SceneC)
-        assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight)
-        assertThat(transition.isInitiatedByUserInput).isTrue()
+        transition = assertThat(layoutState.transitionState).isTransition()
+        assertThat(transition).hasFromScene(SceneA)
+        assertThat(transition).hasToScene(TestScenes.SceneC)
+        assertThat(transition).hasCurrentScene(TestScenes.SceneC)
+        assertThat(transition).hasProgress(56.dp / LayoutHeight)
+        assertThat(transition).isInitiatedByUserInput()
 
         // Wait for the animation to finish. We should now be in scene C.
         rule.waitForIdle()
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC)
     }
 
     @Test
@@ -216,8 +209,8 @@
             TestContent(layoutState)
         }
 
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
 
         // Swipe left (i.e. from right to left) using a velocity of 124 dp/s. We pick 124 dp/s here
         // because 125 dp/s is the velocity threshold from which we commit the gesture. We also use
@@ -233,18 +226,16 @@
 
         // We should be animating back to A (currentScene = SceneA) given that 124 dp/s < velocity
         // threshold.
-        var transition = layoutState.transitionState
-        assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
-        assertThat((transition as TransitionState.Transition).fromScene)
-            .isEqualTo(TestScenes.SceneA)
-        assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
-        assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA)
-        assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth)
+        var transition = assertThat(layoutState.transitionState).isTransition()
+        assertThat(transition).hasFromScene(SceneA)
+        assertThat(transition).hasToScene(SceneB)
+        assertThat(transition).hasCurrentScene(SceneA)
+        assertThat(transition).hasProgress(55.dp / LayoutWidth)
 
         // Wait for the animation to finish. We should now be in scene A.
         rule.waitForIdle()
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
 
         // Now we do the same but vertically and with a swipe velocity of 126dp, which is >
         // velocity threshold. Note that in theory we could have used 125 dp (= velocity threshold)
@@ -259,18 +250,16 @@
         }
 
         // We should be animating to C (currentScene = SceneC).
-        transition = layoutState.transitionState
-        assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
-        assertThat((transition as TransitionState.Transition).fromScene)
-            .isEqualTo(TestScenes.SceneA)
-        assertThat(transition.toScene).isEqualTo(TestScenes.SceneC)
-        assertThat(transition.currentScene).isEqualTo(TestScenes.SceneC)
-        assertThat(transition.progress).isEqualTo(55.dp / LayoutHeight)
+        transition = assertThat(layoutState.transitionState).isTransition()
+        assertThat(transition).hasFromScene(SceneA)
+        assertThat(transition).hasToScene(TestScenes.SceneC)
+        assertThat(transition).hasCurrentScene(TestScenes.SceneC)
+        assertThat(transition).hasProgress(55.dp / LayoutHeight)
 
         // Wait for the animation to finish. We should now be in scene C.
         rule.waitForIdle()
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC)
     }
 
     @Test
@@ -286,8 +275,8 @@
             TestContent(layoutState)
         }
 
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC)
 
         // Swipe down with two fingers.
         rule.onRoot().performTouchInput {
@@ -298,18 +287,16 @@
         }
 
         // We are transitioning to B because we used 2 fingers.
-        val transition = layoutState.transitionState
-        assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
-        assertThat((transition as TransitionState.Transition).fromScene)
-            .isEqualTo(TestScenes.SceneC)
-        assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+        val transition = assertThat(layoutState.transitionState).isTransition()
+        assertThat(transition).hasFromScene(TestScenes.SceneC)
+        assertThat(transition).hasToScene(SceneB)
 
         // Release the fingers and wait for the animation to end. We are back to C because we only
         // swiped 10dp.
         rule.onRoot().performTouchInput { repeat(2) { i -> up(pointerId = i) } }
         rule.waitForIdle()
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC)
     }
 
     @Test
@@ -325,8 +312,8 @@
             TestContent(layoutState)
         }
 
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC)
 
         // Swipe down from the top edge.
         rule.onRoot().performTouchInput {
@@ -335,18 +322,16 @@
         }
 
         // We are transitioning to B (and not A) because we started from the top edge.
-        var transition = layoutState.transitionState
-        assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
-        assertThat((transition as TransitionState.Transition).fromScene)
-            .isEqualTo(TestScenes.SceneC)
-        assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+        var transition = assertThat(layoutState.transitionState).isTransition()
+        assertThat(transition).hasFromScene(TestScenes.SceneC)
+        assertThat(transition).hasToScene(SceneB)
 
         // Release the fingers and wait for the animation to end. We are back to C because we only
         // swiped 10dp.
         rule.onRoot().performTouchInput { up() }
         rule.waitForIdle()
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC)
 
         // Swipe right from the left edge.
         rule.onRoot().performTouchInput {
@@ -355,18 +340,16 @@
         }
 
         // We are transitioning to B (and not A) because we started from the left edge.
-        transition = layoutState.transitionState
-        assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
-        assertThat((transition as TransitionState.Transition).fromScene)
-            .isEqualTo(TestScenes.SceneC)
-        assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+        transition = assertThat(layoutState.transitionState).isTransition()
+        assertThat(transition).hasFromScene(TestScenes.SceneC)
+        assertThat(transition).hasToScene(SceneB)
 
         // Release the fingers and wait for the animation to end. We are back to C because we only
         // swiped 10dp.
         rule.onRoot().performTouchInput { up() }
         rule.waitForIdle()
-        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC)
     }
 
     @Test
@@ -380,7 +363,7 @@
             layoutState(
                 transitions =
                     transitions {
-                        from(TestScenes.SceneA, to = TestScenes.SceneB) {
+                        from(SceneA, to = SceneB) {
                             distance = FixedDistance(verticalSwipeDistance)
                         }
                     }
@@ -395,12 +378,12 @@
                 modifier = Modifier.size(LayoutWidth, LayoutHeight)
             ) {
                 scene(
-                    TestScenes.SceneA,
-                    userActions = mapOf(Swipe.Down to TestScenes.SceneB),
+                    SceneA,
+                    userActions = mapOf(Swipe.Down to SceneB),
                 ) {
                     Spacer(Modifier.fillMaxSize())
                 }
-                scene(TestScenes.SceneB) { Spacer(Modifier.fillMaxSize()) }
+                scene(SceneB) { Spacer(Modifier.fillMaxSize()) }
             }
         }
 
@@ -413,9 +396,9 @@
         }
 
         // We should be at 50%
-        val transition = layoutState.currentTransition
+        val transition = assertThat(layoutState.transitionState).isTransition()
         assertThat(transition).isNotNull()
-        assertThat(transition!!.progress).isEqualTo(0.5f)
+        assertThat(transition).hasProgress(0.5f)
     }
 
     @Test
@@ -434,15 +417,14 @@
         }
 
         // We should still correctly compute that we are swiping down to scene C.
-        var transition = layoutState.currentTransition
-        assertThat(transition).isNotNull()
-        assertThat(transition?.toScene).isEqualTo(TestScenes.SceneC)
+        var transition = assertThat(layoutState.transitionState).isTransition()
+        assertThat(transition).hasToScene(TestScenes.SceneC)
 
         // Release the finger, animating back to scene A.
         rule.onRoot().performTouchInput { up() }
         rule.waitForIdle()
-        assertThat(layoutState.currentTransition).isNull()
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
 
         // Swipe up by exactly touchSlop, so that the drag overSlop is 0f.
         rule.onRoot().performTouchInput {
@@ -451,15 +433,14 @@
         }
 
         // We should still correctly compute that we are swiping up to scene B.
-        transition = layoutState.currentTransition
-        assertThat(transition).isNotNull()
-        assertThat(transition?.toScene).isEqualTo(TestScenes.SceneB)
+        transition = assertThat(layoutState.transitionState).isTransition()
+        assertThat(transition).hasToScene(SceneB)
 
         // Release the finger, animating back to scene A.
         rule.onRoot().performTouchInput { up() }
         rule.waitForIdle()
-        assertThat(layoutState.currentTransition).isNull()
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
 
         // Swipe left by exactly touchSlop, so that the drag overSlop is 0f.
         rule.onRoot().performTouchInput {
@@ -468,14 +449,13 @@
         }
 
         // We should still correctly compute that we are swiping down to scene B.
-        transition = layoutState.currentTransition
-        assertThat(transition).isNotNull()
-        assertThat(transition?.toScene).isEqualTo(TestScenes.SceneB)
+        transition = assertThat(layoutState.transitionState).isTransition()
+        assertThat(transition).hasToScene(SceneB)
     }
 
     @Test
     fun swipeEnabledLater() {
-        val layoutState = MutableSceneTransitionLayoutState(TestScenes.SceneA)
+        val layoutState = MutableSceneTransitionLayoutState(SceneA)
         var swipesEnabled by mutableStateOf(false)
         var touchSlop = 0f
         rule.setContent {
@@ -509,34 +489,32 @@
     fun transitionKey() {
         val transitionkey = TransitionKey(debugName = "foo")
         val state =
-            MutableSceneTransitionLayoutState(
-                TestScenes.SceneA,
+            MutableSceneTransitionLayoutStateImpl(
+                SceneA,
                 transitions {
-                    from(TestScenes.SceneA, to = TestScenes.SceneB) { fade(TestElements.Foo) }
-                    from(TestScenes.SceneA, to = TestScenes.SceneB, key = transitionkey) {
+                    from(SceneA, to = SceneB) { fade(TestElements.Foo) }
+                    from(SceneA, to = SceneB, key = transitionkey) {
                         fade(TestElements.Foo)
                         fade(TestElements.Bar)
                     }
                 }
             )
-                as MutableSceneTransitionLayoutStateImpl
 
         var touchSlop = 0f
         rule.setContent {
             touchSlop = LocalViewConfiguration.current.touchSlop
             SceneTransitionLayout(state, Modifier.size(LayoutWidth, LayoutHeight)) {
                 scene(
-                    TestScenes.SceneA,
+                    SceneA,
                     userActions =
                         mapOf(
-                            Swipe.Down to TestScenes.SceneB,
-                            Swipe.Up to
-                                UserActionResult(TestScenes.SceneB, transitionKey = transitionkey)
+                            Swipe.Down to SceneB,
+                            Swipe.Up to UserActionResult(SceneB, transitionKey = transitionkey)
                         )
                 ) {
                     Box(Modifier.fillMaxSize())
                 }
-                scene(TestScenes.SceneB) { Box(Modifier.fillMaxSize()) }
+                scene(SceneB) { Box(Modifier.fillMaxSize()) }
             }
         }
 
@@ -546,12 +524,12 @@
             moveBy(Offset(0f, touchSlop), delayMillis = 1_000)
         }
 
-        assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue()
+        assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isTrue()
         assertThat(state.currentTransition?.transformationSpec?.transformations).hasSize(1)
 
         // Move the pointer up to swipe to scene B using the new transition.
         rule.onRoot().performTouchInput { moveBy(Offset(0f, -1.dp.toPx()), delayMillis = 1_000) }
-        assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue()
+        assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isTrue()
         assertThat(state.currentTransition?.transformationSpec?.transformations).hasSize(2)
     }
 
@@ -567,19 +545,17 @@
                     // the difference between the bottom of the scene and the bottom of the element,
                     // so that we use the offset and size of the element as well as the size of the
                     // scene.
-                    val fooSize = TestElements.Foo.targetSize(TestScenes.SceneB) ?: return 0f
-                    val fooOffset = TestElements.Foo.targetOffset(TestScenes.SceneB) ?: return 0f
-                    val sceneSize = TestScenes.SceneB.targetSize() ?: return 0f
+                    val fooSize = TestElements.Foo.targetSize(SceneB) ?: return 0f
+                    val fooOffset = TestElements.Foo.targetOffset(SceneB) ?: return 0f
+                    val sceneSize = SceneB.targetSize() ?: return 0f
                     return sceneSize.height - fooOffset.y - fooSize.height
                 }
             }
 
         val state =
             MutableSceneTransitionLayoutState(
-                TestScenes.SceneA,
-                transitions {
-                    from(TestScenes.SceneA, to = TestScenes.SceneB) { distance = swipeDistance }
-                }
+                SceneA,
+                transitions { from(SceneA, to = SceneB) { distance = swipeDistance } }
             )
 
         val layoutSize = 200.dp
@@ -591,10 +567,10 @@
             touchSlop = LocalViewConfiguration.current.touchSlop
 
             SceneTransitionLayout(state, Modifier.size(layoutSize)) {
-                scene(TestScenes.SceneA, userActions = mapOf(Swipe.Up to TestScenes.SceneB)) {
+                scene(SceneA, userActions = mapOf(Swipe.Up to SceneB)) {
                     Box(Modifier.fillMaxSize())
                 }
-                scene(TestScenes.SceneB) {
+                scene(SceneB) {
                     Box(Modifier.fillMaxSize()) {
                         Box(Modifier.offset(y = fooYOffset).element(TestElements.Foo).size(fooSize))
                     }
@@ -611,7 +587,9 @@
         }
 
         rule.waitForIdle()
-        assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue()
-        assertThat(state.currentTransition!!.progress).isWithin(0.01f).of(0.5f)
+        val transition = assertThat(state.transitionState).isTransition()
+        assertThat(transition).hasFromScene(SceneA)
+        assertThat(transition).hasToScene(SceneB)
+        assertThat(transition).hasProgress(0.5f, tolerance = 0.01f)
     }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
new file mode 100644
index 0000000..3489892
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.compose.animation.scene.subjects
+
+import com.android.compose.animation.scene.OverscrollSpec
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionState
+import com.google.common.truth.Fact.simpleFact
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth
+
+/** Assert on a [TransitionState]. */
+fun assertThat(state: TransitionState): TransitionStateSubject {
+    return Truth.assertAbout(TransitionStateSubject.transitionStates()).that(state)
+}
+
+/** Assert on a [TransitionState.Transition]. */
+fun assertThat(transitions: TransitionState.Transition): TransitionSubject {
+    return Truth.assertAbout(TransitionSubject.transitions()).that(transitions)
+}
+
+class TransitionStateSubject
+private constructor(
+    metadata: FailureMetadata,
+    private val actual: TransitionState,
+) : Subject(metadata, actual) {
+    fun hasCurrentScene(sceneKey: SceneKey) {
+        check("currentScene").that(actual.currentScene).isEqualTo(sceneKey)
+    }
+
+    fun isIdle(): TransitionState.Idle {
+        if (actual !is TransitionState.Idle) {
+            failWithActual(simpleFact("expected to be TransitionState.Idle"))
+        }
+
+        return actual as TransitionState.Idle
+    }
+
+    fun isTransition(): TransitionState.Transition {
+        if (actual !is TransitionState.Transition) {
+            failWithActual(simpleFact("expected to be TransitionState.Transition"))
+        }
+
+        return actual as TransitionState.Transition
+    }
+
+    companion object {
+        fun transitionStates() = Factory { metadata, actual: TransitionState ->
+            TransitionStateSubject(metadata, actual)
+        }
+    }
+}
+
+class TransitionSubject
+private constructor(
+    metadata: FailureMetadata,
+    private val actual: TransitionState.Transition,
+) : Subject(metadata, actual) {
+    fun hasCurrentScene(sceneKey: SceneKey) {
+        check("currentScene").that(actual.currentScene).isEqualTo(sceneKey)
+    }
+
+    fun hasFromScene(sceneKey: SceneKey) {
+        check("fromScene").that(actual.fromScene).isEqualTo(sceneKey)
+    }
+
+    fun hasToScene(sceneKey: SceneKey) {
+        check("toScene").that(actual.toScene).isEqualTo(sceneKey)
+    }
+
+    fun hasProgress(progress: Float, tolerance: Float = 0f) {
+        check("progress").that(actual.progress).isWithin(tolerance).of(progress)
+    }
+
+    fun hasProgressVelocity(progressVelocity: Float, tolerance: Float = 0f) {
+        check("progressVelocity")
+            .that(actual.progressVelocity)
+            .isWithin(tolerance)
+            .of(progressVelocity)
+    }
+
+    fun isInitiatedByUserInput() {
+        check("isInitiatedByUserInput").that(actual.isInitiatedByUserInput).isTrue()
+    }
+
+    fun hasIsUserInputOngoing(isUserInputOngoing: Boolean) {
+        check("isUserInputOngoing").that(actual.isUserInputOngoing).isEqualTo(isUserInputOngoing)
+    }
+
+    fun hasOverscrollSpec(): OverscrollSpec {
+        check("currentOverscrollSpec").that(actual.currentOverscrollSpec).isNotNull()
+        return actual.currentOverscrollSpec!!
+    }
+
+    fun hasNoOverscrollSpec() {
+        check("currentOverscrollSpec").that(actual.currentOverscrollSpec).isNull()
+    }
+
+    fun hasBouncingScene(scene: SceneKey) {
+        if (actual !is TransitionState.HasOverscrollProperties) {
+            failWithActual(simpleFact("expected to be TransitionState.HasOverscrollProperties"))
+        }
+
+        check("bouncingScene")
+            .that((actual as TransitionState.HasOverscrollProperties).bouncingScene)
+            .isEqualTo(scene)
+    }
+
+    companion object {
+        fun transitions() = Factory { metadata, actual: TransitionState.Transition ->
+            TransitionSubject(metadata, actual)
+        }
+    }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index f539a23..bdeab79 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -28,16 +28,16 @@
 import android.util.AttributeSet
 import android.util.MathUtils.constrainedMap
 import android.util.TypedValue
-import android.view.View.MeasureSpec.EXACTLY
 import android.view.View
+import android.view.View.MeasureSpec.EXACTLY
 import android.widget.TextView
 import com.android.app.animation.Interpolators
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.animation.GlyphCallback
 import com.android.systemui.animation.TextAnimator
 import com.android.systemui.customization.R
-import com.android.systemui.log.core.LogcatOnlyMessageBuffer
 import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogcatOnlyMessageBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.core.MessageBuffer
 import java.io.PrintWriter
@@ -47,11 +47,13 @@
 import kotlin.math.min
 
 /**
- * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
- * The time's text color is a gradient that changes its colors based on its controller.
+ * Displays the time with the hour positioned above the minutes (ie: 09 above 30 is 9:30). The
+ * time's text color is a gradient that changes its colors based on its controller.
  */
 @SuppressLint("AppCompatCustomView")
-class AnimatableClockView @JvmOverloads constructor(
+class AnimatableClockView
+@JvmOverloads
+constructor(
     context: Context,
     attrs: AttributeSet? = null,
     defStyleAttr: Int = 0,
@@ -63,7 +65,9 @@
         get() = field ?: DEFAULT_LOGGER
     var messageBuffer: MessageBuffer
         get() = logger.buffer
-        set(value) { logger = Logger(value, TAG) }
+        set(value) {
+            logger = Logger(value, TAG)
+        }
 
     var hasCustomPositionUpdatedAnimation: Boolean = false
     var migratedClocks: Boolean = false
@@ -77,16 +81,13 @@
     private var format: CharSequence? = null
     private var descFormat: CharSequence? = null
 
-    @ColorInt
-    private var dozingColor = 0
-
-    @ColorInt
-    private var lockScreenColor = 0
+    @ColorInt private var dozingColor = 0
+    @ColorInt private var lockScreenColor = 0
 
     private var lineSpacingScale = 1f
     private val chargeAnimationDelay: Int
     private var textAnimator: TextAnimator? = null
-    private var onTextAnimatorInitialized: Runnable? = null
+    private var onTextAnimatorInitialized: ((TextAnimator) -> Unit)? = null
 
     private var translateForCenterAnimation = false
     private val parentWidth: Int
@@ -94,9 +95,11 @@
 
     // last text size which is not constrained by view height
     private var lastUnconstrainedTextSize: Float = Float.MAX_VALUE
-    @VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator =
-        { layout, invalidateCb ->
-            TextAnimator(layout, NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb) }
+
+    @VisibleForTesting
+    var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator = { layout, invalidateCb ->
+        TextAnimator(layout, NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb)
+    }
 
     // Used by screenshot tests to provide stability
     @VisibleForTesting var isAnimationEnabled: Boolean = true
@@ -109,40 +112,55 @@
         get() = if (useBoldedVersion()) lockScreenWeightInternal + 100 else lockScreenWeightInternal
 
     /**
-     * The number of pixels below the baseline. For fonts that support languages such as
-     * Burmese, this space can be significant and should be accounted for when computing layout.
+     * The number of pixels below the baseline. For fonts that support languages such as Burmese,
+     * this space can be significant and should be accounted for when computing layout.
      */
-    val bottom get() = paint?.fontMetrics?.bottom ?: 0f
+    val bottom: Float
+        get() = paint?.fontMetrics?.bottom ?: 0f
 
     init {
-        val animatableClockViewAttributes = context.obtainStyledAttributes(
-            attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes
-        )
+        val animatableClockViewAttributes =
+            context.obtainStyledAttributes(
+                attrs,
+                R.styleable.AnimatableClockView,
+                defStyleAttr,
+                defStyleRes
+            )
 
         try {
-            dozingWeightInternal = animatableClockViewAttributes.getInt(
-                R.styleable.AnimatableClockView_dozeWeight,
-                /* default = */ 100
-            )
-            lockScreenWeightInternal = animatableClockViewAttributes.getInt(
-                R.styleable.AnimatableClockView_lockScreenWeight,
-                /* default = */ 300
-            )
-            chargeAnimationDelay = animatableClockViewAttributes.getInt(
-                R.styleable.AnimatableClockView_chargeAnimationDelay, /* default = */ 200
-            )
+            dozingWeightInternal =
+                animatableClockViewAttributes.getInt(
+                    R.styleable.AnimatableClockView_dozeWeight,
+                    /* default = */ 100
+                )
+            lockScreenWeightInternal =
+                animatableClockViewAttributes.getInt(
+                    R.styleable.AnimatableClockView_lockScreenWeight,
+                    /* default = */ 300
+                )
+            chargeAnimationDelay =
+                animatableClockViewAttributes.getInt(
+                    R.styleable.AnimatableClockView_chargeAnimationDelay,
+                    /* default = */ 200
+                )
         } finally {
             animatableClockViewAttributes.recycle()
         }
 
-        val textViewAttributes = context.obtainStyledAttributes(
-            attrs, android.R.styleable.TextView,
-            defStyleAttr, defStyleRes
-        )
+        val textViewAttributes =
+            context.obtainStyledAttributes(
+                attrs,
+                android.R.styleable.TextView,
+                defStyleAttr,
+                defStyleRes
+            )
 
         try {
-            isSingleLineInternal = textViewAttributes.getBoolean(
-                android.R.styleable.TextView_singleLine, /* default = */ false)
+            isSingleLineInternal =
+                textViewAttributes.getBoolean(
+                    android.R.styleable.TextView_singleLine,
+                    /* default = */ false
+                )
         } finally {
             textViewAttributes.recycle()
         }
@@ -156,9 +174,7 @@
         refreshFormat()
     }
 
-    /**
-     * Whether to use a bolded version based on the user specified fontWeightAdjustment.
-     */
+    /** Whether to use a bolded version based on the user specified fontWeightAdjustment. */
     fun useBoldedVersion(): Boolean {
         // "Bold text" fontWeightAdjustment is 300.
         return resources.configuration.fontWeightAdjustment > 100
@@ -169,25 +185,30 @@
         contentDescription = DateFormat.format(descFormat, time)
         val formattedText = DateFormat.format(format, time)
         logger.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() }
-        // Setting text actually triggers a layout pass (because the text view is set to
-        // wrap_content width and TextView always relayouts for this). Avoid needless
-        // relayout if the text didn't actually change.
-        if (!TextUtils.equals(text, formattedText)) {
-            text = formattedText
-            logger.d({ "refreshTime: done setting new time text to: $str1" }) {
-                str1 = formattedText?.toString()
-            }
-            // Because the TextLayout may mutate under the hood as a result of the new text, we
-            // notify the TextAnimator that it may have changed and request a measure/layout. A
-            // crash will occur on the next invocation of setTextStyle if the layout is mutated
-            // without being notified TextInterpolator being notified.
-            if (layout != null) {
-                textAnimator?.updateLayout(layout)
-                logger.d("refreshTime: done updating textAnimator layout")
-            }
-            requestLayout()
-            logger.d("refreshTime: after requestLayout")
+
+        // Setting text actually triggers a layout pass in TextView (because the text view is set to
+        // wrap_content width and TextView always relayouts for this). This avoids needless relayout
+        // if the text didn't actually change.
+        if (TextUtils.equals(text, formattedText)) {
+            return
         }
+
+        text = formattedText
+        logger.d({ "refreshTime: done setting new time text to: $str1" }) {
+            str1 = formattedText?.toString()
+        }
+
+        // Because the TextLayout may mutate under the hood as a result of the new text, we notify
+        // the TextAnimator that it may have changed and request a measure/layout. A crash will
+        // occur on the next invocation of setTextStyle if the layout is mutated without being
+        // notified TextInterpolator being notified.
+        if (layout != null) {
+            textAnimator?.updateLayout(layout)
+            logger.d("refreshTime: done updating textAnimator layout")
+        }
+
+        requestLayout()
+        logger.d("refreshTime: after requestLayout")
     }
 
     fun onTimeZoneChanged(timeZone: TimeZone?) {
@@ -206,19 +227,27 @@
     @SuppressLint("DrawAllocation")
     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
         logger.d("onMeasure")
-        if (migratedClocks && !isSingleLineInternal &&
-                MeasureSpec.getMode(heightMeasureSpec) == EXACTLY) {
+
+        if (
+            migratedClocks &&
+                !isSingleLineInternal &&
+                MeasureSpec.getMode(heightMeasureSpec) == EXACTLY
+        ) {
             // Call straight into TextView.setTextSize to avoid setting lastUnconstrainedTextSize
-            super.setTextSize(TypedValue.COMPLEX_UNIT_PX,
-                    min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F))
+            super.setTextSize(
+                TypedValue.COMPLEX_UNIT_PX,
+                min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F)
+            )
         }
 
         super.onMeasure(widthMeasureSpec, heightMeasureSpec)
         val animator = textAnimator
         if (animator == null) {
-            textAnimator = textAnimatorFactory(layout, ::invalidate)
-            onTextAnimatorInitialized?.run()
-            onTextAnimatorInitialized = null
+            textAnimator =
+                textAnimatorFactory(layout, ::invalidate)?.also {
+                    onTextAnimatorInitialized?.invoke(it)
+                    onTextAnimatorInitialized = null
+                }
         } else {
             animator.updateLayout(layout)
         }
@@ -243,15 +272,13 @@
             canvas.translate(parentWidth / 4f, 0f)
         }
 
-        logger.d({ "onDraw($str1)"}) { str1 = text.toString() }
+        logger.d({ "onDraw($str1)" }) { str1 = text.toString() }
         // intentionally doesn't call super.onDraw here or else the text will be rendered twice
         textAnimator?.draw(canvas)
         canvas.restore()
     }
 
     override fun invalidate() {
-        @Suppress("UNNECESSARY_SAFE_CALL")
-        // logger won't be initialized when called by TextView's constructor
         logger.d("invalidate")
         super.invalidate()
     }
@@ -325,6 +352,7 @@
         if (textAnimator == null) {
             return
         }
+
         logger.d("animateFoldAppear")
         setTextStyle(
             weight = lockScreenWeightInternal,
@@ -348,10 +376,11 @@
     }
 
     fun animateCharge(isDozing: () -> Boolean) {
+        // Skip charge animation if dozing animation is already playing.
         if (textAnimator == null || textAnimator!!.isRunning()) {
-            // Skip charge animation if dozing animation is already playing.
             return
         }
+
         logger.d("animateCharge")
         val startAnimPhase2 = Runnable {
             setTextStyle(
@@ -409,10 +438,9 @@
 
     /**
      * Set text style with an optional animation.
-     *
-     * By passing -1 to weight, the view preserves its current weight.
-     * By passing -1 to textSize, the view preserves its current text size.
-     * By passing null to color, the view preserves its current color.
+     * - By passing -1 to weight, the view preserves its current weight.
+     * - By passing -1 to textSize, the view preserves its current text size.
+     * - By passing null to color, the view preserves its current color.
      *
      * @param weight text weight.
      * @param textSize font size.
@@ -428,8 +456,8 @@
         delay: Long,
         onAnimationEnd: Runnable?
     ) {
-        if (textAnimator != null) {
-            textAnimator?.setTextStyle(
+        textAnimator?.let {
+            it.setTextStyle(
                 weight = weight,
                 textSize = textSize,
                 color = color,
@@ -439,23 +467,24 @@
                 delay = delay,
                 onAnimationEnd = onAnimationEnd
             )
-            textAnimator?.glyphFilter = glyphFilter
-        } else {
-            // when the text animator is set, update its start values
-            onTextAnimatorInitialized = Runnable {
-                textAnimator?.setTextStyle(
-                    weight = weight,
-                    textSize = textSize,
-                    color = color,
-                    animate = false,
-                    duration = duration,
-                    interpolator = interpolator,
-                    delay = delay,
-                    onAnimationEnd = onAnimationEnd
-                )
-                textAnimator?.glyphFilter = glyphFilter
-            }
+            it.glyphFilter = glyphFilter
         }
+            ?: run {
+                // when the text animator is set, update its start values
+                onTextAnimatorInitialized = { textAnimator ->
+                    textAnimator.setTextStyle(
+                        weight = weight,
+                        textSize = textSize,
+                        color = color,
+                        animate = false,
+                        duration = duration,
+                        interpolator = interpolator,
+                        delay = delay,
+                        onAnimationEnd = onAnimationEnd
+                    )
+                    textAnimator.glyphFilter = glyphFilter
+                }
+            }
     }
 
     private fun setTextStyle(
@@ -483,12 +512,13 @@
     fun refreshFormat(use24HourFormat: Boolean) {
         Patterns.update(context)
 
-        format = when {
-            isSingleLineInternal && use24HourFormat -> Patterns.sClockView24
-            !isSingleLineInternal && use24HourFormat -> DOUBLE_LINE_FORMAT_24_HOUR
-            isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
-            else -> DOUBLE_LINE_FORMAT_12_HOUR
-        }
+        format =
+            when {
+                isSingleLineInternal && use24HourFormat -> Patterns.sClockView24
+                !isSingleLineInternal && use24HourFormat -> DOUBLE_LINE_FORMAT_24_HOUR
+                isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
+                else -> DOUBLE_LINE_FORMAT_12_HOUR
+            }
         logger.d({ "refreshFormat($str1)" }) { str1 = format?.toString() }
 
         descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12
@@ -510,10 +540,10 @@
         pw.println("    time=$time")
     }
 
-    private val moveToCenterDelays
+    private val moveToCenterDelays: List<Int>
         get() = if (isLayoutRtl) MOVE_LEFT_DELAYS else MOVE_RIGHT_DELAYS
 
-    private val moveToSideDelays
+    private val moveToSideDelays: List<Int>
         get() = if (isLayoutRtl) MOVE_RIGHT_DELAYS else MOVE_LEFT_DELAYS
 
     /**
@@ -531,7 +561,7 @@
     fun offsetGlyphsForStepClockAnimation(
         clockStartLeft: Int,
         clockMoveDirection: Int,
-        moveFraction: Float
+        moveFraction: Float,
     ) {
         val isMovingToCenter = if (isLayoutRtl) clockMoveDirection < 0 else clockMoveDirection > 0
         val currentMoveAmount = left - clockStartLeft
@@ -558,8 +588,8 @@
      *
      * @param distance is the total distance in pixels to offset the glyphs when animation
      *   completes. Negative distance means we are animating the position towards the center.
-     * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1
-     *   means it finished moving.
+     * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means
+     *   it finished moving.
      */
     fun offsetGlyphsForStepClockAnimation(
         distance: Float,
@@ -568,13 +598,17 @@
         for (i in 0 until NUM_DIGITS) {
             val dir = if (isLayoutRtl) -1 else 1
             val digitFraction =
-                getDigitFraction(digit = i, isMovingToCenter = distance > 0, fraction = fraction)
+                getDigitFraction(
+                    digit = i,
+                    isMovingToCenter = distance > 0,
+                    fraction = fraction,
+                )
             val moveAmountForDigit = dir * distance * digitFraction
             glyphOffsets[i] = moveAmountForDigit
 
             if (distance > 0) {
-                // If distance > 0 then we are moving from the left towards the center.
-                // We need ensure that the glyphs are offset to the initial position.
+                // If distance > 0 then we are moving from the left towards the center. We need to
+                // ensure that the glyphs are offset to the initial position.
                 glyphOffsets[i] -= dir * distance
             }
         }
@@ -582,27 +616,25 @@
     }
 
     private fun getDigitFraction(digit: Int, isMovingToCenter: Boolean, fraction: Float): Float {
-        // The delay for the digit, in terms of fraction (i.e. the digit should not move
-        // during 0.0 - 0.1).
-        val digitInitialDelay =
-            if (isMovingToCenter) {
-                moveToCenterDelays[digit] * MOVE_DIGIT_STEP
-            } else {
-                moveToSideDelays[digit] * MOVE_DIGIT_STEP
-            }
+        // The delay for the digit, in terms of fraction.
+        // (i.e. the digit should not move during 0.0 - 0.1).
+        val delays = if (isMovingToCenter) moveToCenterDelays else moveToSideDelays
+        val digitInitialDelay = delays[digit] * MOVE_DIGIT_STEP
         return MOVE_INTERPOLATOR.getInterpolation(
-                constrainedMap(
-                    0.0f,
-                    1.0f,
-                    digitInitialDelay,
-                    digitInitialDelay + AVAILABLE_ANIMATION_TIME,
-                    fraction,
-                )
+            constrainedMap(
+                /* rangeMin= */ 0.0f,
+                /* rangeMax= */ 1.0f,
+                /* valueMin= */ digitInitialDelay,
+                /* valueMax= */ digitInitialDelay + AVAILABLE_ANIMATION_TIME,
+                /* value= */ fraction,
             )
+        )
     }
 
-    // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
-    // This is an optimization to ensure we only recompute the patterns when the inputs change.
+    /**
+     * DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often. This
+     * is a cache optimization to ensure we only recompute the patterns when the inputs change.
+     */
     private object Patterns {
         var sClockView12: String? = null
         var sClockView24: String? = null
@@ -610,21 +642,22 @@
 
         fun update(context: Context) {
             val locale = Locale.getDefault()
-            val res = context.resources
-            val clockView12Skel = res.getString(R.string.clock_12hr_format)
-            val clockView24Skel = res.getString(R.string.clock_24hr_format)
-            val key = locale.toString() + clockView12Skel + clockView24Skel
-            if (key == sCacheKey) return
-
-            val clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel)
-            sClockView12 = clockView12
-
-            // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton
-            // format.  The following code removes the AM/PM indicator if we didn't want it.
-            if (!clockView12Skel.contains("a")) {
-                sClockView12 = clockView12.replace("a".toRegex(), "").trim { it <= ' ' }
+            val clockView12Skel = context.resources.getString(R.string.clock_12hr_format)
+            val clockView24Skel = context.resources.getString(R.string.clock_24hr_format)
+            val key = "$locale$clockView12Skel$clockView24Skel"
+            if (key == sCacheKey) {
+                return
             }
 
+            sClockView12 =
+                DateFormat.getBestDateTimePattern(locale, clockView12Skel).let {
+                    // CLDR insists on adding an AM/PM indicator even though it wasn't in the format
+                    // string. The following code removes the AM/PM indicator if we didn't want it.
+                    if (!clockView12Skel.contains("a")) {
+                        it.replace("a".toRegex(), "").trim { it <= ' ' }
+                    } else it
+                }
+
             sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel)
             sCacheKey = key
         }
diff --git a/packages/SystemUI/flag_check.py b/packages/SystemUI/flag_check.py
index bac3553..95a25c5 100755
--- a/packages/SystemUI/flag_check.py
+++ b/packages/SystemUI/flag_check.py
@@ -12,19 +12,20 @@
     %s
 
 The Flag: stanza is regex matched and should describe whether your change is behind a flag or flags.
-
-As a CL author, you'll have a consistent place to describe the risk of the proposed change by explicitly calling out the name of the
-flag in addition to its state (ENABLED|DISABLED|DEVELOPMENT|STAGING|TEAMFOOD|TRUNKFOOD|NEXTFOOD).
+As a CL author, you'll have a consistent place to describe the risk of the proposed change by explicitly calling out the name of the flag.
+For legacy flags use EXEMPT with your flag name.
 
 Some examples below:
 
-Flag: NONE
-Flag: NA
-Flag: LEGACY ENABLE_ONE_SEARCH DISABLED
-Flag: ACONFIG com.android.launcher3.enable_twoline_allapps DEVELOPMENT
-Flag: ACONFIG com.android.launcher3.enable_twoline_allapps TRUNKFOOD
+Flag: NONE Repohook Update
+Flag: TEST_ONLY
+Flag: EXEMPT resource only update
+Flag: EXEMPT bugfix
+Flag: EXEMPT refactor
+Flag: com.android.launcher3.enable_twoline_allapps
+Flag: com.google.android.apps.nexuslauncher.zero_state_web_data_loader
 
-Check the git history for more examples. It's a regex matched field.
+Check the git history for more examples. It's a regex matched field. See go/android-flag-directive for more details on various formats.
 """
 
 def main():
@@ -63,28 +64,31 @@
         return
 
     field = 'Flag'
-    none = '(NONE|NA|N\/A)' # NONE|NA|N/A
+    none = 'NONE'
+    testOnly = 'TEST_ONLY'
+    docsOnly = 'DOCS_ONLY'
+    exempt = 'EXEMPT'
+    justification = '<justification>'
 
-    typeExpression = '\s*(LEGACY|ACONFIG)' # [type:LEGACY|ACONFIG]
-
-    # legacyFlagName contains only uppercase alphabets with '_' - Ex: ENABLE_ONE_SEARCH
-    # Aconfig Flag name format = "packageName"."flagName"
+    # Aconfig Flag name format = <packageName>.<flagName>
     # package name - Contains only lowercase alphabets + digits + '.' - Ex: com.android.launcher3
-    # For now alphabets, digits, "_", "." characters are allowed in flag name and not adding stricter format check.
+    # For now alphabets, digits, "_", "." characters are allowed in flag name.
+    # Checks if there is "one dot" between packageName and flagName and not adding stricter format check
     #common_typos_disable
-    flagName = '([a-zA-z0-9_.])+'
+    flagName = '([a-zA-Z0-9.]+)([.]+)([a-zA-Z0-9_.]+)'
 
-    #[state:ENABLED|DISABLED|DEVELOPMENT|TEAM*(TEAMFOOD)|STAGING|TRUNK*(TRUNK_STAGING, TRUNK_FOOD)|NEXT*(NEXTFOOD)]
-    stateExpression = '\s*(ENABLED|DISABLED|DEVELOPMENT|TEAM[a-zA-z]*|STAGING|TRUNK[a-zA-z]*|NEXT[a-zA-z]*)'
+    # None and Exempt needs justification
+    exemptRegex = fr'{exempt}\s*[a-zA-Z]+'
+    noneRegex = fr'{none}\s*[a-zA-Z]+'
     #common_typos_enable
 
-    readableRegexMsg = '\n\tFlag: (NONE|NA)\n\tFlag: LEGACY|ACONFIG FlagName|packageName.flagName ENABLED|DISABLED|DEVELOPMENT|TEAMFOOD|STAGING|TRUNKFOOD|NEXTFOOD'
+    readableRegexMsg = '\n\tFlag: '+none+' '+justification+'\n\tFlag: <packageName>.<flagName>\n\tFlag: ' +exempt+' '+justification+'\n\tFlag: '+testOnly+'\n\tFlag: '+docsOnly
 
     flagRegex = fr'^{field}: .*$'
     check_flag = re.compile(flagRegex) #Flag:
 
     # Ignore case for flag name format.
-    flagNameRegex = fr'(?i)^{field}:\s*({none}|{typeExpression}\s*{flagName}\s*{stateExpression})\s*'
+    flagNameRegex = fr'(?i)^{field}:\s*({noneRegex}|{flagName}|{testOnly}|{docsOnly}|{exemptRegex})\s*'
     check_flagName = re.compile(flagNameRegex) #Flag: <flag name format>
 
     flagError = False
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
index 47a00f4..624f18d 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
@@ -133,7 +133,7 @@
         @ColorInt seed: Int,
         darkTheme: Boolean,
         style: Style
-    ) : this(seed, darkTheme, style, 0.5)
+    ) : this(seed, darkTheme, style, 0.0)
 
     @JvmOverloads
     constructor(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 447c280..6c3f3c1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -17,8 +17,11 @@
 
 package com.android.keyguard
 
+import android.app.admin.DevicePolicyManager
+import android.app.admin.flags.Flags as DevicePolicyFlags
 import android.content.res.Configuration
 import android.media.AudioManager
+import android.platform.test.annotations.EnableFlags
 import android.telephony.TelephonyManager
 import android.testing.TestableLooper.RunWithLooper
 import android.testing.TestableResources
@@ -148,6 +151,7 @@
     @Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate
     @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
     @Mock private lateinit var postureController: DevicePostureController
+    @Mock private lateinit var devicePolicyManager: DevicePolicyManager
 
     @Captor
     private lateinit var swipeListenerArgumentCaptor:
@@ -273,6 +277,7 @@
                 mSelectedUserInteractor,
                 deviceProvisionedController,
                 faceAuthAccessibilityDelegate,
+                devicePolicyManager,
                 keyguardTransitionInteractor,
                 { primaryBouncerInteractor },
             ) {
@@ -934,6 +939,45 @@
         verify(viewFlipperController).asynchronouslyInflateView(any(), any(), any())
     }
 
+    @Test
+    @EnableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES)
+    fun showAlmostAtWipeDialog_calledOnMainUser_setsCorrectUserType() {
+        val mainUserId = 10
+
+        underTest.showMessageForFailedUnlockAttempt(
+            /* userId = */ mainUserId,
+            /* expiringUserId = */ mainUserId,
+            /* mainUserId = */ mainUserId,
+            /* remainingBeforeWipe = */ 1,
+            /* failedAttempts = */ 1
+        )
+
+        verify(view)
+            .showAlmostAtWipeDialog(any(), any(), eq(KeyguardSecurityContainer.USER_TYPE_PRIMARY))
+    }
+
+    @Test
+    @EnableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES)
+    fun showAlmostAtWipeDialog_calledOnNonMainUser_setsCorrectUserType() {
+        val secondaryUserId = 10
+        val mainUserId = 0
+
+        underTest.showMessageForFailedUnlockAttempt(
+            /* userId = */ secondaryUserId,
+            /* expiringUserId = */ secondaryUserId,
+            /* mainUserId = */ mainUserId,
+            /* remainingBeforeWipe = */ 1,
+            /* failedAttempts = */ 1
+        )
+
+        verify(view)
+            .showAlmostAtWipeDialog(
+                any(),
+                any(),
+                eq(KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER)
+            )
+    }
+
     private val registeredSwipeListener: KeyguardSecurityContainer.SwipeListener
         get() {
             underTest.onViewAttached()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
index 3d8159e..9c9ee53 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
@@ -24,7 +24,6 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.util.settings.FakeSettings
-import com.google.common.truth.Truth
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
@@ -66,7 +65,7 @@
 
             runCurrent()
 
-            Truth.assertThat(actualValue).isFalse()
+            assertThat(actualValue).isFalse()
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt
new file mode 100644
index 0000000..5757f67
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt
@@ -0,0 +1,219 @@
+/*
+ * 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.data.repository
+
+import android.hardware.display.ColorDisplayManager
+import android.hardware.display.NightDisplayListener
+import android.os.UserHandle
+import android.provider.Settings
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.utils.UserScopedService
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.fakeGlobalSettings
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.utils.leaks.FakeLocationController
+import com.google.common.truth.Truth.assertThat
+import java.time.LocalTime
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NightDisplayRepositoryTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val testUser = UserHandle.of(1)!!
+    private val testStartTime = LocalTime.MIDNIGHT
+    private val testEndTime = LocalTime.NOON
+    private val colorDisplayManager =
+        mock<ColorDisplayManager> {
+            whenever(nightDisplayAutoMode).thenReturn(ColorDisplayManager.AUTO_MODE_DISABLED)
+            whenever(isNightDisplayActivated).thenReturn(false)
+            whenever(nightDisplayCustomStartTime).thenReturn(testStartTime)
+            whenever(nightDisplayCustomEndTime).thenReturn(testEndTime)
+        }
+    private val locationController = FakeLocationController(LeakCheck())
+    private val nightDisplayListener = mock<NightDisplayListener>()
+    private val listenerBuilder =
+        mock<NightDisplayListenerModule.Builder> {
+            whenever(setUser(ArgumentMatchers.anyInt())).thenReturn(this)
+            whenever(build()).thenReturn(nightDisplayListener)
+        }
+    private val globalSettings = kosmos.fakeGlobalSettings
+    private val secureSettings = kosmos.fakeSettings
+    private val testDispatcher = StandardTestDispatcher()
+    private val scope = TestScope(testDispatcher)
+    private val userScopedColorDisplayManager =
+        mock<UserScopedService<ColorDisplayManager>> {
+            whenever(forUser(eq(testUser))).thenReturn(colorDisplayManager)
+        }
+
+    private val underTest =
+        NightDisplayRepository(
+            testDispatcher,
+            scope.backgroundScope,
+            globalSettings,
+            secureSettings,
+            listenerBuilder,
+            userScopedColorDisplayManager,
+            locationController,
+        )
+
+    @Test
+    fun nightDisplayState_matchesAutoMode() =
+        scope.runTest {
+            enrollInForcedNightDisplayAutoMode(INITIALLY_FORCE_AUTO_MODE, testUser)
+            val callbackCaptor = argumentCaptor<NightDisplayListener.Callback>()
+            val lastState by collectLastValue(underTest.nightDisplayState(testUser))
+
+            runCurrent()
+
+            verify(nightDisplayListener).setCallback(callbackCaptor.capture())
+            val callback = callbackCaptor.value
+
+            assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_DISABLED)
+
+            callback.onAutoModeChanged(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME)
+            assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME)
+
+            callback.onCustomStartTimeChanged(testStartTime)
+            assertThat(lastState!!.startTime).isEqualTo(testStartTime)
+
+            callback.onCustomEndTimeChanged(testEndTime)
+            assertThat(lastState!!.endTime).isEqualTo(testEndTime)
+
+            callback.onAutoModeChanged(ColorDisplayManager.AUTO_MODE_TWILIGHT)
+
+            assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_TWILIGHT)
+        }
+
+    @Test
+    fun nightDisplayState_matchesIsNightDisplayActivated() =
+        scope.runTest {
+            enrollInForcedNightDisplayAutoMode(INITIALLY_FORCE_AUTO_MODE, testUser)
+
+            val callbackCaptor = argumentCaptor<NightDisplayListener.Callback>()
+
+            val lastState by collectLastValue(underTest.nightDisplayState(testUser))
+            runCurrent()
+
+            verify(nightDisplayListener).setCallback(callbackCaptor.capture())
+            val callback = callbackCaptor.value
+            assertThat(lastState!!.isActivated)
+                .isEqualTo(colorDisplayManager.isNightDisplayActivated)
+
+            callback.onActivated(true)
+            assertThat(lastState!!.isActivated).isTrue()
+
+            callback.onActivated(false)
+            assertThat(lastState!!.isActivated).isFalse()
+        }
+
+    @Test
+    fun nightDisplayState_matchesController_initiallyCustomAutoMode() =
+        scope.runTest {
+            whenever(colorDisplayManager.nightDisplayAutoMode)
+                .thenReturn(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME)
+            enrollInForcedNightDisplayAutoMode(INITIALLY_FORCE_AUTO_MODE, testUser)
+
+            val lastState by collectLastValue(underTest.nightDisplayState(testUser))
+            runCurrent()
+
+            assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME)
+        }
+
+    @Test
+    fun nightDisplayState_matchesController_initiallyTwilightAutoMode() =
+        scope.runTest {
+            whenever(colorDisplayManager.nightDisplayAutoMode)
+                .thenReturn(ColorDisplayManager.AUTO_MODE_TWILIGHT)
+            enrollInForcedNightDisplayAutoMode(INITIALLY_FORCE_AUTO_MODE, testUser)
+
+            val lastState by collectLastValue(underTest.nightDisplayState(testUser))
+            runCurrent()
+
+            assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_TWILIGHT)
+        }
+
+    /**
+     * When the value of the raw auto mode is missing the call to nightDisplayState should not crash
+     */
+    @Test
+    fun nightDisplayState_whenAutoModeSettingIsNotInitialized_loadsDataWithoutException() =
+        scope.runTest {
+            // only auto mode_available is set, and the raw auto_mode has nothing set
+            globalSettings.putString(
+                Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE,
+                NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE
+            )
+
+            val lastState by collectLastValue(underTest.nightDisplayState(testUser))
+            runCurrent()
+
+            assertThat(lastState!!.shouldForceAutoMode).isTrue()
+        }
+
+    @Test
+    fun nightDisplayState_matchesForceAutoMode() =
+        scope.runTest {
+            enrollInForcedNightDisplayAutoMode(false, testUser)
+            val lastState by collectLastValue(underTest.nightDisplayState(testUser))
+            runCurrent()
+
+            assertThat(lastState!!.shouldForceAutoMode).isEqualTo(false)
+
+            enrollInForcedNightDisplayAutoMode(true, testUser)
+            assertThat(lastState!!.shouldForceAutoMode).isEqualTo(true)
+        }
+
+    private fun enrollInForcedNightDisplayAutoMode(enroll: Boolean, userHandle: UserHandle) {
+        globalSettings.putString(
+            Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE,
+            if (enroll) NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE
+            else NIGHT_DISPLAY_FORCED_AUTO_MODE_UNAVAILABLE
+        )
+        secureSettings.putIntForUser(
+            Settings.Secure.NIGHT_DISPLAY_AUTO_MODE,
+            if (enroll) NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET else NIGHT_DISPLAY_AUTO_MODE_RAW_SET,
+            userHandle.identifier
+        )
+    }
+
+    private companion object {
+        const val INITIALLY_FORCE_AUTO_MODE = false
+        const val NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE = "1"
+        const val NIGHT_DISPLAY_FORCED_AUTO_MODE_UNAVAILABLE = "0"
+        const val NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET = -1
+        const val NIGHT_DISPLAY_AUTO_MODE_RAW_SET = 0
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt
new file mode 100644
index 0000000..c0d481c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt
@@ -0,0 +1,138 @@
+/*
+ * 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.data.repository
+
+import android.os.UserHandle
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
+class OneHandedModeRepositoryImplTest : SysuiTestCase() {
+
+    private val testUser1 = UserHandle.of(1)!!
+    private val testUser2 = UserHandle.of(2)!!
+    private val testDispatcher = StandardTestDispatcher()
+    private val scope = TestScope(testDispatcher)
+    private val settings: FakeSettings = FakeSettings()
+
+    private val underTest: OneHandedModeRepository =
+        OneHandedModeRepositoryImpl(
+            testDispatcher,
+            scope.backgroundScope,
+            settings,
+        )
+
+    @Test
+    fun isEnabled_settingNotInitialized_returnsFalseByDefault() =
+        scope.runTest {
+            val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+
+            runCurrent()
+
+            assertThat(actualValue).isFalse()
+        }
+
+    @Test
+    fun isEnabled_initiallyGetsSettingsValue() =
+        scope.runTest {
+            val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+
+            settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
+            runCurrent()
+
+            assertThat(actualValue).isTrue()
+        }
+
+    @Test
+    fun isEnabled_settingUpdated_valueUpdated() =
+        scope.runTest {
+            val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+            runCurrent()
+            assertThat(actualValue).isFalse()
+
+            settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
+            runCurrent()
+
+            assertThat(actualValue).isTrue()
+            runCurrent()
+
+            settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
+            runCurrent()
+            assertThat(actualValue).isFalse()
+        }
+
+    @Test
+    fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() =
+        scope.runTest {
+            val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1))
+            val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2))
+
+            settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
+            settings.putIntForUser(SETTING_NAME, DISABLED, testUser2.identifier)
+            runCurrent()
+            assertThat(lastValueUser1).isFalse()
+            assertThat(lastValueUser2).isFalse()
+
+            settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
+            runCurrent()
+            assertThat(lastValueUser1).isTrue()
+            assertThat(lastValueUser2).isFalse()
+        }
+
+    @Test
+    fun setEnabled() =
+        scope.runTest {
+            val success = underTest.setIsEnabled(true, testUser1)
+            runCurrent()
+            assertThat(success).isTrue()
+
+            val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
+            assertThat(actualValue).isEqualTo(ENABLED)
+        }
+
+    @Test
+    fun setDisabled() =
+        scope.runTest {
+            val success = underTest.setIsEnabled(false, testUser1)
+            runCurrent()
+            assertThat(success).isTrue()
+
+            val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
+            assertThat(actualValue).isEqualTo(DISABLED)
+        }
+
+    companion object {
+        private const val SETTING_NAME = Settings.Secure.ONE_HANDED_MODE_ENABLED
+        private const val DISABLED = 0
+        private const val ENABLED = 1
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
index 11a4241..27bffd0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
@@ -18,10 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.view.GestureDetector;
 import android.view.MotionEvent;
@@ -30,6 +28,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 
@@ -37,7 +36,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
@@ -51,89 +49,66 @@
     CentralSurfaces mCentralSurfaces;
 
     @Mock
+    ShadeViewController mShadeViewController;
+
+    @Mock
     TouchHandler.TouchSession mTouchSession;
 
     ShadeTouchHandler mTouchHandler;
 
-    @Captor
-    ArgumentCaptor<GestureDetector.OnGestureListener> mGestureListenerCaptor;
-    @Captor
-    ArgumentCaptor<InputChannelCompat.InputEventListener> mInputListenerCaptor;
-
     private static final int TOUCH_HEIGHT = 20;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-
-        mTouchHandler = new ShadeTouchHandler(Optional.of(mCentralSurfaces), TOUCH_HEIGHT);
-    }
-
-    // Verifies that a swipe down in the gesture region is captured by the shade touch handler.
-    @Test
-    public void testSwipeDown_captured() {
-        final boolean captured = swipe(Direction.DOWN);
-
-        assertThat(captured).isTrue();
-    }
-
-    // Verifies that a swipe in the upward direction is not catpured.
-    @Test
-    public void testSwipeUp_notCaptured() {
-        final boolean captured = swipe(Direction.UP);
-
-        // Motion events not captured as the swipe is going in the wrong direction.
-        assertThat(captured).isFalse();
-    }
-
-    // Verifies that a swipe down forwards captured touches to the shade window for handling.
-    @Test
-    public void testSwipeDown_sentToShadeWindow() {
-        swipe(Direction.DOWN);
-
-        // Both motion events are sent for the shade window to process.
-        verify(mCentralSurfaces, times(2)).handleExternalShadeWindowTouch(any());
-    }
-
-    // Verifies that a swipe down is not forwarded to the shade window.
-    @Test
-    public void testSwipeUp_touchesNotSent() {
-        swipe(Direction.UP);
-
-        // Motion events are not sent for the shade window to process as the swipe is going in the
-        // wrong direction.
-        verify(mCentralSurfaces, never()).handleExternalShadeWindowTouch(any());
+        mTouchHandler = new ShadeTouchHandler(Optional.of(mCentralSurfaces), mShadeViewController,
+                TOUCH_HEIGHT);
     }
 
     /**
-     * Simulates a swipe in the given direction and returns true if the touch was intercepted by the
-     * touch handler's gesture listener.
-     * <p>
-     * Swipe down starts from a Y coordinate of 0 and goes downward. Swipe up starts from the edge
-     * of the gesture region, {@link #TOUCH_HEIGHT}, and goes upward to 0.
+     * Verify that touches aren't handled when the bouncer is showing.
      */
-    private boolean swipe(Direction direction) {
-        Mockito.clearInvocations(mTouchSession);
+    @Test
+    public void testInactiveOnBouncer() {
+        when(mCentralSurfaces.isBouncerShowing()).thenReturn(true);
         mTouchHandler.onSessionStart(mTouchSession);
-
-        verify(mTouchSession).registerGestureListener(mGestureListenerCaptor.capture());
-        verify(mTouchSession).registerInputListener(mInputListenerCaptor.capture());
-
-        final float startY = direction == Direction.UP ? TOUCH_HEIGHT : 0;
-        final float endY = direction == Direction.UP ? 0 : TOUCH_HEIGHT;
-
-        // Send touches to the input and gesture listener.
-        final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, startY, 0);
-        final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, endY, 0);
-        mInputListenerCaptor.getValue().onInputEvent(event1);
-        mInputListenerCaptor.getValue().onInputEvent(event2);
-        final boolean captured = mGestureListenerCaptor.getValue().onScroll(event1, event2, 0,
-                startY - endY);
-
-        return captured;
+        verify(mTouchSession).pop();
     }
 
-    private enum Direction {
-        DOWN, UP,
+    /**
+     * Make sure {@link ShadeTouchHandler}
+     */
+    @Test
+    public void testTouchPilferingOnScroll() {
+        final MotionEvent motionEvent1 = Mockito.mock(MotionEvent.class);
+        final MotionEvent motionEvent2 = Mockito.mock(MotionEvent.class);
+
+        final ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerArgumentCaptor =
+                ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+
+        mTouchHandler.onSessionStart(mTouchSession);
+        verify(mTouchSession).registerGestureListener(gestureListenerArgumentCaptor.capture());
+
+        assertThat(gestureListenerArgumentCaptor.getValue()
+                .onScroll(motionEvent1, motionEvent2, 1, 1))
+                .isTrue();
     }
+
+    /**
+     * Ensure touches are propagated to the {@link ShadeViewController}.
+     */
+    @Test
+    public void testEventPropagation() {
+        final MotionEvent motionEvent = Mockito.mock(MotionEvent.class);
+
+        final ArgumentCaptor<InputChannelCompat.InputEventListener>
+                inputEventListenerArgumentCaptor =
+                    ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+
+        mTouchHandler.onSessionStart(mTouchSession);
+        verify(mTouchSession).registerInputListener(inputEventListenerArgumentCaptor.capture());
+        inputEventListenerArgumentCaptor.getValue().onInputEvent(motionEvent);
+        verify(mShadeViewController).handleExternalTouch(motionEvent);
+    }
+
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 81878aa..0c5e726 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.authentication.domain.interactor
 
 import android.app.admin.DevicePolicyManager
+import android.app.admin.flags.Flags as DevicePolicyFlags
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
@@ -32,6 +34,8 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -410,12 +414,16 @@
         }
 
     @Test
+    @EnableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES)
     fun upcomingWipe() =
         testScope.runTest {
             val upcomingWipe by collectLastValue(underTest.upcomingWipe)
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
             val wrongPin = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 }
+            kosmos.fakeUserRepository.asMainUser()
+            kosmos.fakeAuthenticationRepository.profileWithMinFailedUnlockAttemptsForWipe =
+                FakeUserRepository.MAIN_USER_ID
 
             underTest.authenticate(correctPin)
             assertThat(upcomingWipe).isNull()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
index f4ad764..9b1d4ec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -84,7 +84,6 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
-import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor;
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel;
@@ -161,8 +160,6 @@
     @Mock
     private InteractionJankMonitor mInteractionJankMonitor;
     @Mock
-    private PromptCredentialInteractor mBiometricPromptCredentialInteractor;
-    @Mock
     private PromptSelectorInteractor mPromptSelectionInteractor;
     @Mock
     private CredentialViewModel mCredentialViewModel;
@@ -1057,7 +1054,6 @@
 
     private final class TestableAuthController extends AuthController {
         private int mBuildCount = 0;
-        private PromptInfo mLastBiometricPromptInfo;
 
         TestableAuthController(Context context) {
             super(context, null /* applicationCoroutineScope */,
@@ -1065,8 +1061,8 @@
                     mFingerprintManager, mFaceManager, () -> mUdfpsController, mDisplayManager,
                     mWakefulnessLifecycle, mPanelInteractionDetector, mUserManager,
                     mLockPatternUtils, () -> mUdfpsLogger, () -> mLogContextInteractor,
-                    () -> mBiometricPromptCredentialInteractor, () -> mPromptSelectionInteractor,
-                    () -> mCredentialViewModel, () -> mPromptViewModel, mInteractionJankMonitor,
+                    () -> mPromptSelectionInteractor, () -> mCredentialViewModel,
+                    () -> mPromptViewModel, mInteractionJankMonitor,
                     mHandler, mBackgroundExecutor, mUdfpsUtils, mVibratorHelper);
         }
 
@@ -1079,8 +1075,6 @@
                 UserManager userManager,
                 LockPatternUtils lockPatternUtils, PromptViewModel viewModel) {
 
-            mLastBiometricPromptInfo = promptInfo;
-
             AuthDialog dialog;
             if (mBuildCount == 0) {
                 dialog = mDialog1;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index ecfcc90..a5acf72 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -66,15 +66,12 @@
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
-    private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
-    private val sceneContainerStartable = kosmos.sceneContainerStartable
 
     private lateinit var underTest: BouncerViewModel
 
     @Before
     fun setUp() {
-        sceneContainerStartable.start()
+        kosmos.sceneContainerStartable.start()
         underTest = kosmos.bouncerViewModel
     }
 
@@ -164,11 +161,11 @@
             assertThat(isInputEnabled).isTrue()
 
             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
-                bouncerInteractor.authenticate(WRONG_PIN)
+                kosmos.bouncerInteractor.authenticate(WRONG_PIN)
             }
             assertThat(isInputEnabled).isFalse()
 
-            val lockoutEndMs = authenticationInteractor.lockoutEndTimestamp ?: 0
+            val lockoutEndMs = kosmos.authenticationInteractor.lockoutEndTimestamp ?: 0
             advanceTimeBy(lockoutEndMs - testScope.currentTime)
             assertThat(isInputEnabled).isTrue()
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt
index 312c14d..fec56ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt
@@ -18,9 +18,13 @@
 
 import android.content.applicationContext
 import android.os.UserManager
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
+import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf
 import androidx.test.filters.SmallTest
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+import com.android.systemui.Flags.FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testDispatcher
@@ -40,15 +44,19 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class BrightnessPolicyRepositoryImplTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class BrightnessPolicyRepositoryImplTest(flags: FlagsParameterization) : SysuiTestCase() {
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
 
     private val kosmos = testKosmos()
 
-    private val fakeUserRepository = kosmos.fakeUserRepository
-
     private val mockUserRestrictionChecker: UserRestrictionChecker = mock {
         whenever(checkIfRestrictionEnforced(any(), anyString(), anyInt())).thenReturn(null)
         whenever(hasBaseUserRestriction(any(), anyString(), anyInt())).thenReturn(false)
@@ -130,7 +138,83 @@
             }
         }
 
-    private companion object {
-        val RESTRICTION = UserManager.DISALLOW_CONFIG_BRIGHTNESS
+    @Test
+    @DisableFlags(FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION)
+    fun brightnessBaseUserRestriction_flagOff_noRestriction() =
+        with(kosmos) {
+            testScope.runTest {
+                whenever(
+                        mockUserRestrictionChecker.hasBaseUserRestriction(
+                            any(),
+                            eq(RESTRICTION),
+                            eq(userRepository.getSelectedUserInfo().id)
+                        )
+                    )
+                    .thenReturn(true)
+
+                val restrictions by collectLastValue(underTest.restrictionPolicy)
+
+                assertThat(restrictions).isEqualTo(PolicyRestriction.NoRestriction)
+            }
+        }
+
+    @Test
+    fun bothRestrictions_returnsSetEnforcedAdminFromCheck() =
+        with(kosmos) {
+            testScope.runTest {
+                val enforcedAdmin: EnforcedAdmin =
+                    EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(RESTRICTION)
+
+                whenever(
+                        mockUserRestrictionChecker.checkIfRestrictionEnforced(
+                            any(),
+                            eq(RESTRICTION),
+                            eq(userRepository.getSelectedUserInfo().id)
+                        )
+                    )
+                    .thenReturn(enforcedAdmin)
+
+                whenever(
+                        mockUserRestrictionChecker.hasBaseUserRestriction(
+                            any(),
+                            eq(RESTRICTION),
+                            eq(userRepository.getSelectedUserInfo().id)
+                        )
+                    )
+                    .thenReturn(true)
+
+                val restrictions by collectLastValue(underTest.restrictionPolicy)
+
+                assertThat(restrictions).isEqualTo(PolicyRestriction.Restricted(enforcedAdmin))
+            }
+        }
+
+    @Test
+    @EnableFlags(FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION)
+    fun brightnessBaseUserRestriction_flagOn_emptyRestriction() =
+        with(kosmos) {
+            testScope.runTest {
+                whenever(
+                        mockUserRestrictionChecker.hasBaseUserRestriction(
+                            any(),
+                            eq(RESTRICTION),
+                            eq(userRepository.getSelectedUserInfo().id)
+                        )
+                    )
+                    .thenReturn(true)
+
+                val restrictions by collectLastValue(underTest.restrictionPolicy)
+
+                assertThat(restrictions).isEqualTo(PolicyRestriction.Restricted(EnforcedAdmin()))
+            }
+        }
+
+    companion object {
+        private const val RESTRICTION = UserManager.DISALLOW_CONFIG_BRIGHTNESS
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return allCombinationsOf(FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION)
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt
index 85a4bcf..11f5238 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt
@@ -48,7 +48,6 @@
     private val kosmos = testKosmos()
 
     private val mockActivityStarter = kosmos.activityStarter
-    private val fakeBrightnessPolicyEnforcementInteractor = kosmos.fakeBrightnessPolicyRepository
 
     private val underTest =
         with(kosmos) {
@@ -70,7 +69,18 @@
 
                 fakeBrightnessPolicyRepository.setCurrentUserRestricted()
 
-                assertThat(restriction).isInstanceOf(PolicyRestriction.Restricted::class.java)
+                assertThat(restriction)
+                    .isEqualTo(
+                        PolicyRestriction.Restricted(
+                            EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(
+                                BrightnessPolicyRepository.RESTRICTION
+                            )
+                        )
+                    )
+
+                fakeBrightnessPolicyRepository.setBaseUserRestriction()
+
+                assertThat(restriction).isEqualTo(PolicyRestriction.Restricted(EnforcedAdmin()))
             }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt
index 2386957..7628deb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt
@@ -50,6 +50,7 @@
     @Mock private lateinit var context: Context
     @Mock private lateinit var packageManager: PackageManager
     @Mock private lateinit var handler: Handler
+    @Mock private lateinit var packageInstallerMonitor: PackageInstallerMonitor
 
     private lateinit var repository: PackageChangeRepository
     private lateinit var updateMonitor: PackageUpdateMonitor
@@ -60,19 +61,20 @@
             MockitoAnnotations.initMocks(this@PackageChangeRepositoryTest)
             whenever(context.packageManager).thenReturn(packageManager)
 
-            repository = PackageChangeRepositoryImpl { user ->
-                updateMonitor =
-                    PackageUpdateMonitor(
-                        user = user,
-                        bgDispatcher = testDispatcher,
-                        scope = applicationCoroutineScope,
-                        context = context,
-                        bgHandler = handler,
-                        logger = PackageUpdateLogger(logcatLogBuffer()),
-                        systemClock = fakeSystemClock,
-                    )
-                updateMonitor
-            }
+            repository =
+                PackageChangeRepositoryImpl(packageInstallerMonitor) { user ->
+                    updateMonitor =
+                        PackageUpdateMonitor(
+                            user = user,
+                            bgDispatcher = testDispatcher,
+                            scope = applicationCoroutineScope,
+                            context = context,
+                            bgHandler = handler,
+                            logger = PackageUpdateLogger(logcatLogBuffer()),
+                            systemClock = fakeSystemClock,
+                        )
+                    updateMonitor
+                }
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt
new file mode 100644
index 0000000..5556b04
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt
@@ -0,0 +1,228 @@
+/*
+ * 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.common.data.repository
+
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageInstaller.SessionInfo
+import android.graphics.Bitmap
+import android.os.fakeExecutorHandler
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.PackageInstallSession
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Correspondence
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PackageInstallerMonitorTest : SysuiTestCase() {
+    @Mock private lateinit var packageInstaller: PackageInstaller
+    @Mock private lateinit var icon1: Bitmap
+    @Mock private lateinit var icon2: Bitmap
+    @Mock private lateinit var icon3: Bitmap
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val handler = kosmos.fakeExecutorHandler
+
+    private lateinit var session1: SessionInfo
+    private lateinit var session2: SessionInfo
+    private lateinit var session3: SessionInfo
+
+    private lateinit var defaultSessions: List<SessionInfo>
+
+    private lateinit var underTest: PackageInstallerMonitor
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        session1 =
+            SessionInfo().apply {
+                sessionId = 1
+                appPackageName = "pkg_name_1"
+                appIcon = icon1
+            }
+        session2 =
+            SessionInfo().apply {
+                sessionId = 2
+                appPackageName = "pkg_name_2"
+                appIcon = icon2
+            }
+        session3 =
+            SessionInfo().apply {
+                sessionId = 3
+                appPackageName = "pkg_name_3"
+                appIcon = icon3
+            }
+        defaultSessions = listOf(session1, session2)
+
+        whenever(packageInstaller.allSessions).thenReturn(defaultSessions)
+        whenever(packageInstaller.getSessionInfo(1)).thenReturn(session1)
+        whenever(packageInstaller.getSessionInfo(2)).thenReturn(session2)
+
+        underTest =
+            PackageInstallerMonitor(
+                handler,
+                kosmos.applicationCoroutineScope,
+                logcatLogBuffer("PackageInstallerRepositoryImplTest"),
+                packageInstaller,
+            )
+    }
+
+    @Test
+    fun installSessions_callbacksRegisteredOnlyWhenFlowIsCollected() =
+        testScope.runTest {
+            // Verify callback not added before flow is collected
+            verify(packageInstaller, never()).registerSessionCallback(any(), eq(handler))
+
+            // Start collecting the flow
+            val job =
+                backgroundScope.launch {
+                    underTest.installSessionsForPrimaryUser.collect {
+                        // Do nothing with the value
+                    }
+                }
+            runCurrent()
+
+            // Verify callback added only after flow is collected
+            val callback =
+                withArgCaptor<PackageInstaller.SessionCallback> {
+                    verify(packageInstaller).registerSessionCallback(capture(), eq(handler))
+                }
+
+            // Verify callback not removed
+            verify(packageInstaller, never()).unregisterSessionCallback(any())
+
+            // Stop collecting the flow
+            job.cancel()
+            runCurrent()
+
+            // Verify callback removed once flow stops being collected
+            verify(packageInstaller).unregisterSessionCallback(eq(callback))
+        }
+
+    @Test
+    fun installSessions_newSessionsAreAdded() =
+        testScope.runTest {
+            val installSessions by collectLastValue(underTest.installSessionsForPrimaryUser)
+            assertThat(installSessions)
+                .comparingElementsUsing(represents)
+                .containsExactlyElementsIn(defaultSessions)
+
+            val callback =
+                withArgCaptor<PackageInstaller.SessionCallback> {
+                    verify(packageInstaller).registerSessionCallback(capture(), eq(handler))
+                }
+
+            // New session added
+            whenever(packageInstaller.getSessionInfo(3)).thenReturn(session3)
+            callback.onCreated(3)
+            runCurrent()
+
+            // Verify flow updated with the new session
+            assertThat(installSessions)
+                .comparingElementsUsing(represents)
+                .containsExactlyElementsIn(defaultSessions + session3)
+        }
+
+    @Test
+    fun installSessions_finishedSessionsAreRemoved() =
+        testScope.runTest {
+            val installSessions by collectLastValue(underTest.installSessionsForPrimaryUser)
+            assertThat(installSessions)
+                .comparingElementsUsing(represents)
+                .containsExactlyElementsIn(defaultSessions)
+
+            val callback =
+                withArgCaptor<PackageInstaller.SessionCallback> {
+                    verify(packageInstaller).registerSessionCallback(capture(), eq(handler))
+                }
+
+            // Session 1 finished successfully
+            callback.onFinished(1, /* success = */ true)
+            runCurrent()
+
+            // Verify flow updated with session 1 removed
+            assertThat(installSessions)
+                .comparingElementsUsing(represents)
+                .containsExactlyElementsIn(defaultSessions - session1)
+        }
+
+    @Test
+    fun installSessions_sessionsUpdatedOnBadgingChange() =
+        testScope.runTest {
+            val installSessions by collectLastValue(underTest.installSessionsForPrimaryUser)
+            assertThat(installSessions)
+                .comparingElementsUsing(represents)
+                .containsExactlyElementsIn(defaultSessions)
+
+            val callback =
+                withArgCaptor<PackageInstaller.SessionCallback> {
+                    verify(packageInstaller).registerSessionCallback(capture(), eq(handler))
+                }
+
+            // App icon for session 1 updated
+            val newSession =
+                SessionInfo().apply {
+                    sessionId = 1
+                    appPackageName = "pkg_name_1"
+                    appIcon = mock()
+                }
+            whenever(packageInstaller.getSessionInfo(1)).thenReturn(newSession)
+            callback.onBadgingChanged(1)
+            runCurrent()
+
+            // Verify flow updated with the new session 1
+            assertThat(installSessions)
+                .comparingElementsUsing(represents)
+                .containsExactlyElementsIn(defaultSessions - session1 + newSession)
+        }
+
+    private val represents =
+        Correspondence.from<PackageInstallSession, SessionInfo>(
+            { actual, expected ->
+                actual?.sessionId == expected?.sessionId &&
+                    actual?.packageName == expected?.appPackageName &&
+                    actual?.icon == expected?.getAppIcon()
+            },
+            "represents",
+        )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
index def63ec..bfed33c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
@@ -16,12 +16,16 @@
 
 package com.android.systemui.communal
 
+import android.platform.test.annotations.EnableFlags
 import android.service.dream.dreamManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -62,6 +66,7 @@
                     powerInteractor = kosmos.powerInteractor,
                     keyguardInteractor = kosmos.keyguardInteractor,
                     keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+                    communalInteractor = kosmos.communalInteractor,
                     dreamManager = dreamManager,
                     bgScope = kosmos.applicationCoroutineScope,
                 )
@@ -84,6 +89,32 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE)
+    fun restartDreamingWhenTransitioningFromDreamingToOccludedToDreaming() =
+        testScope.runTest {
+            keyguardRepository.setDreaming(false)
+            powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+            whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true)
+            runCurrent()
+
+            verify(dreamManager, never()).startDream()
+
+            kosmos.fakeKeyguardRepository.setKeyguardOccluded(true)
+            kosmos.fakeKeyguardRepository.setDreaming(true)
+            runCurrent()
+
+            transition(from = KeyguardState.DREAMING, to = KeyguardState.OCCLUDED)
+            kosmos.fakeKeyguardRepository.setKeyguardOccluded(false)
+            kosmos.fakeKeyguardRepository.setDreaming(false)
+            runCurrent()
+
+            transition(from = KeyguardState.OCCLUDED, to = KeyguardState.DREAMING)
+            runCurrent()
+
+            verify(dreamManager).startDream()
+        }
+
+    @Test
     fun shouldNotStartDreamWhenIneligibleToDream() =
         testScope.runTest {
             keyguardRepository.setDreaming(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index b4b812d..0ab0959 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -265,7 +265,7 @@
         with(kosmos) {
             testScope.runTest {
                 // Device is dreaming and on communal.
-                fakeKeyguardRepository.setDreaming(true)
+                updateDreaming(true)
                 communalInteractor.changeScene(CommunalScenes.Communal)
 
                 val scene by collectLastValue(communalInteractor.desiredScene)
@@ -282,7 +282,7 @@
         with(kosmos) {
             testScope.runTest {
                 // Device is not dreaming and on communal.
-                fakeKeyguardRepository.setDreaming(false)
+                updateDreaming(false)
                 communalInteractor.changeScene(CommunalScenes.Communal)
 
                 // Scene stays as Communal
@@ -297,7 +297,7 @@
         with(kosmos) {
             testScope.runTest {
                 // Device is dreaming and on communal.
-                fakeKeyguardRepository.setDreaming(true)
+                updateDreaming(true)
                 communalInteractor.changeScene(CommunalScenes.Communal)
 
                 val scene by collectLastValue(communalInteractor.desiredScene)
@@ -309,7 +309,7 @@
 
                 // Dream stops, timeout is cancelled and device stays on hub, because the regular
                 // screen timeout will take effect at this point.
-                fakeKeyguardRepository.setDreaming(false)
+                updateDreaming(false)
                 advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
             }
@@ -320,7 +320,7 @@
         with(kosmos) {
             testScope.runTest {
                 // Device is on communal, but not dreaming.
-                fakeKeyguardRepository.setDreaming(false)
+                updateDreaming(false)
                 communalInteractor.changeScene(CommunalScenes.Communal)
 
                 val scene by collectLastValue(communalInteractor.desiredScene)
@@ -328,7 +328,7 @@
 
                 // Wait a bit, but not long enough to timeout, then start dreaming.
                 advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
-                fakeKeyguardRepository.setDreaming(true)
+                updateDreaming(true)
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 // Device times out after one screen timeout interval, dream doesn't reset timeout.
@@ -338,11 +338,31 @@
         }
 
     @Test
+    fun hubTimeout_dreamAfterInitialTimeout_goesToBlank() =
+        with(kosmos) {
+            testScope.runTest {
+                // Device is on communal.
+                communalInteractor.changeScene(CommunalScenes.Communal)
+
+                // Device stays on the hub after the timeout since we're not dreaming.
+                advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2)
+                val scene by collectLastValue(communalInteractor.desiredScene)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+                // Start dreaming.
+                updateDreaming(true)
+
+                // Hub times out immediately.
+                assertThat(scene).isEqualTo(CommunalScenes.Blank)
+            }
+        }
+
+    @Test
     fun hubTimeout_userActivityTriggered_resetsTimeout() =
         with(kosmos) {
             testScope.runTest {
                 // Device is dreaming and on communal.
-                fakeKeyguardRepository.setDreaming(true)
+                updateDreaming(true)
                 communalInteractor.changeScene(CommunalScenes.Communal)
 
                 val scene by collectLastValue(communalInteractor.desiredScene)
@@ -371,7 +391,7 @@
                 fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2)
 
                 // Device is dreaming and on communal.
-                fakeKeyguardRepository.setDreaming(true)
+                updateDreaming(true)
                 communalInteractor.changeScene(CommunalScenes.Communal)
 
                 val scene by collectLastValue(communalInteractor.desiredScene)
@@ -395,6 +415,12 @@
             runCurrent()
         }
 
+    private fun TestScope.updateDreaming(dreaming: Boolean) =
+        with(kosmos) {
+            fakeKeyguardRepository.setDreaming(dreaming)
+            runCurrent()
+        }
+
     private suspend fun TestScope.enableCommunal() =
         with(kosmos) {
             setCommunalAvailable(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index 1cdc2b6..407bf4c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -114,7 +114,7 @@
 
             // Change to media unavailable and notify the listener.
             whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false)
-            mediaDataListenerCaptor.value.onMediaDataRemoved("key")
+            mediaDataListenerCaptor.value.onMediaDataRemoved("key", false)
             runCurrent()
 
             // Media active now returns false.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
index ce7b60e..325a324 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
@@ -29,6 +29,7 @@
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.settingslib.flags.Flags.FLAG_ALLOW_ALL_WIDGETS_ON_LOCKSCREEN_BY_DEFAULT
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.broadcastDispatcher
@@ -202,6 +203,18 @@
                 .isEqualTo(AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
         }
 
+    @EnableFlags(FLAG_COMMUNAL_HUB, FLAG_ALLOW_ALL_WIDGETS_ON_LOCKSCREEN_BY_DEFAULT)
+    @Test
+    fun hubShowsAllWidgetsByDefaultWhenFlagEnabled() =
+        testScope.runTest {
+            val setting by collectLastValue(underTest.getWidgetCategories(PRIMARY_USER))
+            assertThat(setting?.categories)
+                .isEqualTo(
+                    AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD +
+                        AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN
+                )
+        }
+
     private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
         whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
             .thenReturn(disabledFlags)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index fe4d32d..6ce6cdb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -17,16 +17,18 @@
 package com.android.systemui.communal.data.repository
 
 import android.app.backup.BackupManager
-import android.appwidget.AppWidgetManager
 import android.appwidget.AppWidgetProviderInfo
 import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL
 import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
 import android.content.ComponentName
 import android.content.applicationContext
+import android.graphics.Bitmap
 import android.os.UserHandle
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.data.repository.fakePackageChangeRepository
+import com.android.systemui.common.shared.model.PackageInstallSession
 import com.android.systemui.communal.data.backup.CommunalBackupUtils
 import com.android.systemui.communal.data.db.CommunalItemRank
 import com.android.systemui.communal.data.db.CommunalWidgetDao
@@ -46,10 +48,10 @@
 import com.android.systemui.res.R
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 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.flow.MutableStateFlow
 import kotlinx.coroutines.test.runCurrent
@@ -58,7 +60,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
@@ -68,10 +69,10 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
-    @Mock private lateinit var appWidgetManager: AppWidgetManager
     @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
-    @Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo
     @Mock private lateinit var providerInfoA: AppWidgetProviderInfo
+    @Mock private lateinit var providerInfoB: AppWidgetProviderInfo
+    @Mock private lateinit var providerInfoC: AppWidgetProviderInfo
     @Mock private lateinit var communalWidgetHost: CommunalWidgetHost
     @Mock private lateinit var communalWidgetDao: CommunalWidgetDao
     @Mock private lateinit var backupManager: BackupManager
@@ -79,9 +80,11 @@
     private lateinit var backupUtils: CommunalBackupUtils
     private lateinit var logBuffer: LogBuffer
     private lateinit var fakeWidgets: MutableStateFlow<Map<CommunalItemRank, CommunalWidgetItem>>
+    private lateinit var fakeProviders: MutableStateFlow<Map<Int, AppWidgetProviderInfo?>>
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
+    private val packageChangeRepository = kosmos.fakePackageChangeRepository
 
     private val fakeAllowlist =
         listOf(
@@ -96,6 +99,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         fakeWidgets = MutableStateFlow(emptyMap())
+        fakeProviders = MutableStateFlow(emptyMap())
         logBuffer = logcatLogBuffer(name = "CommunalWidgetRepoImplTest")
         backupUtils = CommunalBackupUtils(kosmos.applicationContext)
 
@@ -103,12 +107,11 @@
 
         overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())
 
-        whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch")
         whenever(communalWidgetDao.getWidgets()).thenReturn(fakeWidgets)
+        whenever(communalWidgetHost.appWidgetProviders).thenReturn(fakeProviders)
 
         underTest =
             CommunalWidgetRepositoryImpl(
-                Optional.of(appWidgetManager),
                 appWidgetHost,
                 testScope.backgroundScope,
                 kosmos.testDispatcher,
@@ -117,6 +120,7 @@
                 logBuffer,
                 backupManager,
                 backupUtils,
+                packageChangeRepository,
             )
     }
 
@@ -126,15 +130,13 @@
             val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1)
             val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L)
             fakeWidgets.value = mapOf(communalItemRankEntry to communalWidgetItemEntry)
-            whenever(appWidgetManager.getAppWidgetInfo(anyInt())).thenReturn(providerInfoA)
-
-            installedProviders(listOf(stopwatchProviderInfo))
+            fakeProviders.value = mapOf(1 to providerInfoA)
 
             val communalWidgets by collectLastValue(underTest.communalWidgets)
             verify(communalWidgetDao).getWidgets()
             assertThat(communalWidgets)
                 .containsExactly(
-                    CommunalWidgetContentModel(
+                    CommunalWidgetContentModel.Available(
                         appWidgetId = communalWidgetItemEntry.widgetId,
                         providerInfo = providerInfoA,
                         priority = communalItemRankEntry.rank,
@@ -146,6 +148,102 @@
         }
 
     @Test
+    fun communalWidgets_widgetsWithoutMatchingProvidersAreSkipped() =
+        testScope.runTest {
+            // Set up 4 widgets, but widget 3 and 4 don't have matching providers
+            fakeWidgets.value =
+                mapOf(
+                    CommunalItemRank(uid = 1L, rank = 1) to
+                        CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L),
+                    CommunalItemRank(uid = 2L, rank = 2) to
+                        CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L),
+                    CommunalItemRank(uid = 3L, rank = 3) to
+                        CommunalWidgetItem(uid = 3L, 3, "pk_3/cls_3", 3L),
+                    CommunalItemRank(uid = 4L, rank = 4) to
+                        CommunalWidgetItem(uid = 4L, 4, "pk_4/cls_4", 4L),
+                )
+            fakeProviders.value =
+                mapOf(
+                    1 to providerInfoA,
+                    2 to providerInfoB,
+                )
+
+            // Expect to see only widget 1 and 2
+            val communalWidgets by collectLastValue(underTest.communalWidgets)
+            assertThat(communalWidgets)
+                .containsExactly(
+                    CommunalWidgetContentModel.Available(
+                        appWidgetId = 1,
+                        providerInfo = providerInfoA,
+                        priority = 1,
+                    ),
+                    CommunalWidgetContentModel.Available(
+                        appWidgetId = 2,
+                        providerInfo = providerInfoB,
+                        priority = 2,
+                    ),
+                )
+        }
+
+    @Test
+    fun communalWidgets_updatedWhenProvidersUpdate() =
+        testScope.runTest {
+            // Set up widgets and providers
+            fakeWidgets.value =
+                mapOf(
+                    CommunalItemRank(uid = 1L, rank = 1) to
+                        CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L),
+                    CommunalItemRank(uid = 2L, rank = 2) to
+                        CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L),
+                )
+            fakeProviders.value =
+                mapOf(
+                    1 to providerInfoA,
+                    2 to providerInfoB,
+                )
+
+            // Expect two widgets
+            val communalWidgets by collectLastValue(underTest.communalWidgets)
+            assertThat(communalWidgets).isNotNull()
+            assertThat(communalWidgets)
+                .containsExactly(
+                    CommunalWidgetContentModel.Available(
+                        appWidgetId = 1,
+                        providerInfo = providerInfoA,
+                        priority = 1,
+                    ),
+                    CommunalWidgetContentModel.Available(
+                        appWidgetId = 2,
+                        providerInfo = providerInfoB,
+                        priority = 2,
+                    ),
+                )
+
+            // Provider info updated for widget 1
+            fakeProviders.value =
+                mapOf(
+                    1 to providerInfoC,
+                    2 to providerInfoB,
+                )
+            runCurrent()
+
+            assertThat(communalWidgets)
+                .containsExactly(
+                    CommunalWidgetContentModel.Available(
+                        appWidgetId = 1,
+                        // Verify that provider info updated
+                        providerInfo = providerInfoC,
+                        priority = 1,
+                    ),
+                    CommunalWidgetContentModel.Available(
+                        appWidgetId = 2,
+                        providerInfo = providerInfoB,
+                        priority = 2,
+                    ),
+                )
+        }
+
+    @Test
     fun addWidget_allocateId_bindWidget_andAddToDb() =
         testScope.runTest {
             val provider = ComponentName("pkg_name", "cls_name")
@@ -434,9 +532,102 @@
             assertThat(restoredWidget2.rank).isEqualTo(expectedWidget2.rank)
         }
 
-    private fun installedProviders(providers: List<AppWidgetProviderInfo>) {
-        whenever(appWidgetManager.installedProviders).thenReturn(providers)
-    }
+    @Test
+    fun pendingWidgets() =
+        testScope.runTest {
+            fakeWidgets.value =
+                mapOf(
+                    CommunalItemRank(uid = 1L, rank = 1) to
+                        CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L),
+                    CommunalItemRank(uid = 2L, rank = 2) to
+                        CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L),
+                )
+
+            // Widget 1 is installed
+            fakeProviders.value = mapOf(1 to providerInfoA)
+
+            // Widget 2 is pending install
+            val fakeIcon = mock<Bitmap>()
+            packageChangeRepository.setInstallSessions(
+                listOf(
+                    PackageInstallSession(
+                        sessionId = 1,
+                        packageName = "pk_2",
+                        icon = fakeIcon,
+                        user = UserHandle.CURRENT,
+                    )
+                )
+            )
+
+            val communalWidgets by collectLastValue(underTest.communalWidgets)
+            assertThat(communalWidgets)
+                .containsExactly(
+                    CommunalWidgetContentModel.Available(
+                        appWidgetId = 1,
+                        providerInfo = providerInfoA,
+                        priority = 1,
+                    ),
+                    CommunalWidgetContentModel.Pending(
+                        appWidgetId = 2,
+                        priority = 2,
+                        packageName = "pk_2",
+                        icon = fakeIcon,
+                        user = UserHandle.CURRENT,
+                    ),
+                )
+        }
+
+    @Test
+    fun pendingWidgets_pendingWidgetBecomesAvailableAfterInstall() =
+        testScope.runTest {
+            fakeWidgets.value =
+                mapOf(
+                    CommunalItemRank(uid = 1L, rank = 1) to
+                        CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L),
+                )
+
+            // Widget 1 is pending install
+            val fakeIcon = mock<Bitmap>()
+            packageChangeRepository.setInstallSessions(
+                listOf(
+                    PackageInstallSession(
+                        sessionId = 1,
+                        packageName = "pk_1",
+                        icon = fakeIcon,
+                        user = UserHandle.CURRENT,
+                    )
+                )
+            )
+
+            val communalWidgets by collectLastValue(underTest.communalWidgets)
+            assertThat(communalWidgets)
+                .containsExactly(
+                    CommunalWidgetContentModel.Pending(
+                        appWidgetId = 1,
+                        priority = 1,
+                        packageName = "pk_1",
+                        icon = fakeIcon,
+                        user = UserHandle.CURRENT,
+                    ),
+                )
+
+            // Package for widget 1 finished installing
+            packageChangeRepository.setInstallSessions(emptyList())
+
+            // Provider info for widget 1 becomes available
+            fakeProviders.value = mapOf(1 to providerInfoA)
+
+            runCurrent()
+
+            assertThat(communalWidgets)
+                .containsExactly(
+                    CommunalWidgetContentModel.Available(
+                        appWidgetId = 1,
+                        providerInfo = providerInfoA,
+                        priority = 1,
+                    ),
+                )
+        }
 
     private fun setAppWidgetIds(ids: List<Int>) {
         whenever(appWidgetHost.appWidgetIds).thenReturn(ids.toIntArray())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 456fb79..83227e1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -23,6 +23,7 @@
 import android.appwidget.AppWidgetProviderInfo
 import android.content.Intent
 import android.content.pm.UserInfo
+import android.graphics.Bitmap
 import android.os.UserHandle
 import android.os.UserManager
 import android.os.userManager
@@ -203,14 +204,14 @@
         }
 
     @Test
-    fun isCommunalAvailable_whenDreaming_true() =
+    fun isCommunalAvailable_whenKeyguardShowing_true() =
         testScope.runTest {
             val isAvailable by collectLastValue(underTest.isCommunalAvailable)
             assertThat(isAvailable).isFalse()
 
             keyguardRepository.setIsEncryptedOrLockdown(false)
             userRepository.setSelectedUserInfo(mainUser)
-            keyguardRepository.setDreaming(true)
+            keyguardRepository.setKeyguardShowing(true)
 
             assertThat(isAvailable).isTrue()
         }
@@ -871,7 +872,14 @@
             // One widget is filtered out and the remaining two link to main user id.
             assertThat(checkNotNull(widgetContent).size).isEqualTo(2)
             widgetContent!!.forEachIndexed { _, model ->
-                assertThat(model.providerInfo.profile?.identifier).isEqualTo(MAIN_USER_INFO.id)
+                assertThat(model is CommunalContentModel.WidgetContent.Widget).isTrue()
+                assertThat(
+                        (model as CommunalContentModel.WidgetContent.Widget)
+                            .providerInfo
+                            .profile
+                            ?.identifier
+                    )
+                    .isEqualTo(MAIN_USER_INFO.id)
             }
         }
 
@@ -1037,9 +1045,9 @@
             runCurrent()
 
             val widgetContent by collectLastValue(underTest.widgetContent)
-            // Given three widgets, and one of them is associated with work profile.
+            // One available work widget, one pending work widget, and one regular available widget.
             val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
-            val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
+            val widget2 = createPendingWidgetForUser(2, userId = USER_INFO_WORK.id)
             val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
             val widgets = listOf(widget1, widget2, widget3)
             widgetRepository.setCommunalWidgets(widgets)
@@ -1049,11 +1057,9 @@
                 DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
             )
 
-            // Widget under work profile is filtered out and the remaining two link to main user id.
-            assertThat(widgetContent).hasSize(2)
-            widgetContent!!.forEach { model ->
-                assertThat(model.providerInfo.profile?.identifier).isEqualTo(MAIN_USER_INFO.id)
-            }
+            // Widgets under work profile are filtered out. Only the regular widget remains.
+            assertThat(widgetContent).hasSize(1)
+            assertThat(widgetContent?.get(0)?.appWidgetId).isEqualTo(3)
         }
 
     @Test
@@ -1076,7 +1082,7 @@
             val widgetContent by collectLastValue(underTest.widgetContent)
             // Given three widgets, and one of them is associated with work profile.
             val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
-            val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
+            val widget2 = createPendingWidgetForUser(2, userId = USER_INFO_WORK.id)
             val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
             val widgets = listOf(widget1, widget2, widget3)
             widgetRepository.setCommunalWidgets(widgets)
@@ -1086,10 +1092,11 @@
                 DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE
             )
 
-            // Widget under work profile is available.
+            // Widgets under work profile are available.
             assertThat(widgetContent).hasSize(3)
-            assertThat(widgetContent!![0].providerInfo.profile?.identifier)
-                .isEqualTo(USER_INFO_WORK.id)
+            assertThat(widgetContent?.get(0)?.appWidgetId).isEqualTo(1)
+            assertThat(widgetContent?.get(1)?.appWidgetId).isEqualTo(2)
+            assertThat(widgetContent?.get(2)?.appWidgetId).isEqualTo(3)
         }
 
     @Test
@@ -1182,8 +1189,11 @@
         )
     }
 
-    private fun createWidgetForUser(appWidgetId: Int, userId: Int): CommunalWidgetContentModel =
-        mock<CommunalWidgetContentModel> {
+    private fun createWidgetForUser(
+        appWidgetId: Int,
+        userId: Int
+    ): CommunalWidgetContentModel.Available =
+        mock<CommunalWidgetContentModel.Available> {
             whenever(this.appWidgetId).thenReturn(appWidgetId)
             val providerInfo =
                 mock<AppWidgetProviderInfo>().apply {
@@ -1193,11 +1203,27 @@
             whenever(this.providerInfo).thenReturn(providerInfo)
         }
 
+    private fun createPendingWidgetForUser(
+        appWidgetId: Int,
+        priority: Int = 0,
+        packageName: String = "",
+        icon: Bitmap? = null,
+        userId: Int = 0,
+    ): CommunalWidgetContentModel.Pending {
+        return CommunalWidgetContentModel.Pending(
+            appWidgetId = appWidgetId,
+            priority = priority,
+            packageName = packageName,
+            icon = icon,
+            user = UserHandle(userId),
+        )
+    }
+
     private fun createWidgetWithCategory(
         appWidgetId: Int,
         category: Int
     ): CommunalWidgetContentModel =
-        mock<CommunalWidgetContentModel> {
+        mock<CommunalWidgetContentModel.Available> {
             whenever(this.appWidgetId).thenReturn(appWidgetId)
             val providerInfo = mock<AppWidgetProviderInfo>().apply { widgetCategory = category }
             whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
index 02d927a..f9d5073 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
@@ -86,4 +86,26 @@
             )
             assertThat(isUmoOnCommunal).isFalse()
         }
+
+    @Test
+    fun testIsUmoOnCommunalDuringTransitionBetweenOccludedAndGlanceableHub() =
+        testScope.runTest {
+            val isUmoOnCommunal by collectLastValue(underTest.isUmoOnCommunal)
+            assertThat(isUmoOnCommunal).isNull()
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.OCCLUDED,
+                to = KeyguardState.GLANCEABLE_HUB,
+                testScope
+            )
+            assertThat(isUmoOnCommunal).isTrue()
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GLANCEABLE_HUB,
+                to = KeyguardState.OCCLUDED,
+                testScope
+            )
+
+            assertThat(isUmoOnCommunal).isFalse()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
index 89a4c04..b3a12a6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
@@ -28,6 +28,8 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -36,6 +38,9 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -96,4 +101,137 @@
 
             assertThat(appWidgetIdToRemove).isEqualTo(2)
         }
+
+    @Test
+    fun observer_onHostStartListeningTriggeredWhileObserverActive() =
+        testScope.runTest {
+            // Observer added
+            val observer = mock<CommunalAppWidgetHost.Observer>()
+            underTest.addObserver(observer)
+            runCurrent()
+
+            // Verify callback triggered
+            verify(observer, never()).onHostStartListening()
+            underTest.startListening()
+            runCurrent()
+            verify(observer).onHostStartListening()
+
+            clearInvocations(observer)
+
+            // Observer removed
+            underTest.removeObserver(observer)
+            runCurrent()
+
+            // Verify callback not triggered
+            underTest.startListening()
+            runCurrent()
+            verify(observer, never()).onHostStartListening()
+        }
+
+    @Test
+    fun observer_onHostStopListeningTriggeredWhileObserverActive() =
+        testScope.runTest {
+            // Observer added
+            val observer = mock<CommunalAppWidgetHost.Observer>()
+            underTest.addObserver(observer)
+            runCurrent()
+
+            // Verify callback triggered
+            verify(observer, never()).onHostStopListening()
+            underTest.stopListening()
+            runCurrent()
+            verify(observer).onHostStopListening()
+
+            clearInvocations(observer)
+
+            // Observer removed
+            underTest.removeObserver(observer)
+            runCurrent()
+
+            // Verify callback not triggered
+            underTest.stopListening()
+            runCurrent()
+            verify(observer, never()).onHostStopListening()
+        }
+
+    @Test
+    fun observer_onAllocateAppWidgetIdTriggeredWhileObserverActive() =
+        testScope.runTest {
+            // Observer added
+            val observer = mock<CommunalAppWidgetHost.Observer>()
+            underTest.addObserver(observer)
+            runCurrent()
+
+            // Verify callback triggered
+            verify(observer, never()).onAllocateAppWidgetId(any())
+            val id = underTest.allocateAppWidgetId()
+            runCurrent()
+            verify(observer).onAllocateAppWidgetId(eq(id))
+
+            clearInvocations(observer)
+
+            // Observer removed
+            underTest.removeObserver(observer)
+            runCurrent()
+
+            // Verify callback not triggered
+            underTest.allocateAppWidgetId()
+            runCurrent()
+            verify(observer, never()).onAllocateAppWidgetId(any())
+        }
+
+    @Test
+    fun observer_onDeleteAppWidgetIdTriggeredWhileObserverActive() =
+        testScope.runTest {
+            // Observer added
+            val observer = mock<CommunalAppWidgetHost.Observer>()
+            underTest.addObserver(observer)
+            runCurrent()
+
+            // Verify callback triggered
+            verify(observer, never()).onDeleteAppWidgetId(any())
+            underTest.deleteAppWidgetId(1)
+            runCurrent()
+            verify(observer).onDeleteAppWidgetId(eq(1))
+
+            clearInvocations(observer)
+
+            // Observer removed
+            underTest.removeObserver(observer)
+            runCurrent()
+
+            // Verify callback not triggered
+            underTest.deleteAppWidgetId(2)
+            runCurrent()
+            verify(observer, never()).onDeleteAppWidgetId(any())
+        }
+
+    @Test
+    fun observer_multipleObservers() =
+        testScope.runTest {
+            // Set up two observers
+            val observer1 = mock<CommunalAppWidgetHost.Observer>()
+            val observer2 = mock<CommunalAppWidgetHost.Observer>()
+            underTest.addObserver(observer1)
+            underTest.addObserver(observer2)
+            runCurrent()
+
+            // Verify both observers triggered
+            verify(observer1, never()).onHostStartListening()
+            verify(observer2, never()).onHostStartListening()
+            underTest.startListening()
+            runCurrent()
+            verify(observer1).onHostStartListening()
+            verify(observer2).onHostStartListening()
+
+            // Observer 1 removed
+            underTest.removeObserver(observer1)
+            runCurrent()
+
+            // Verify only observer 2 is triggered
+            underTest.stopListening()
+            runCurrent()
+            verify(observer2).onHostStopListening()
+            verify(observer1, never()).onHostStopListening()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 9aebc30..6ca04df 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -123,12 +123,12 @@
             // Widgets available.
             val widgets =
                 listOf(
-                    CommunalWidgetContentModel(
+                    CommunalWidgetContentModel.Available(
                         appWidgetId = 0,
                         priority = 30,
                         providerInfo = providerInfo,
                     ),
-                    CommunalWidgetContentModel(
+                    CommunalWidgetContentModel.Available(
                         appWidgetId = 1,
                         priority = 20,
                         providerInfo = providerInfo,
@@ -177,12 +177,12 @@
             // Widgets available.
             val widgets =
                 listOf(
-                    CommunalWidgetContentModel(
+                    CommunalWidgetContentModel.Available(
                         appWidgetId = 0,
                         priority = 30,
                         providerInfo = providerInfo,
                     ),
-                    CommunalWidgetContentModel(
+                    CommunalWidgetContentModel.Available(
                         appWidgetId = 1,
                         priority = 20,
                         providerInfo = providerInfo,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 569116c..be44339 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -44,7 +44,6 @@
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
 import com.android.systemui.communal.ui.viewmodel.PopupType
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
 import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -52,6 +51,7 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -90,7 +90,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     @Mock private lateinit var mediaHost: MediaHost
     @Mock private lateinit var user: UserInfo
     @Mock private lateinit var providerInfo: AppWidgetProviderInfo
@@ -111,7 +111,7 @@
     private lateinit var underTest: CommunalViewModel
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
@@ -142,10 +142,10 @@
                 testScope,
                 context.resources,
                 kosmos.keyguardTransitionInteractor,
+                kosmos.keyguardInteractor,
                 kosmos.communalInteractor,
                 kosmos.communalTutorialInteractor,
                 kosmos.shadeInteractor,
-                kosmos.deviceEntryInteractor,
                 mediaHost,
                 logcatLogBuffer("CommunalViewModelTest"),
             )
@@ -186,12 +186,12 @@
             // Widgets available.
             val widgets =
                 listOf(
-                    CommunalWidgetContentModel(
+                    CommunalWidgetContentModel.Available(
                         appWidgetId = 0,
                         priority = 30,
                         providerInfo = providerInfo,
                     ),
-                    CommunalWidgetContentModel(
+                    CommunalWidgetContentModel.Available(
                         appWidgetId = 1,
                         priority = 20,
                         providerInfo = providerInfo,
@@ -245,7 +245,7 @@
 
             widgetRepository.setCommunalWidgets(
                 listOf(
-                    CommunalWidgetContentModel(
+                    CommunalWidgetContentModel.Available(
                         appWidgetId = 1,
                         priority = 1,
                         providerInfo = providerInfo,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
index 6cae5d3..3d2eabf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
@@ -18,6 +18,7 @@
 
 import android.appwidget.AppWidgetProviderInfo
 import android.content.pm.UserInfo
+import android.graphics.Bitmap
 import android.os.UserHandle
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -60,6 +61,7 @@
     private val kosmos = testKosmos()
 
     @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
+    @Mock private lateinit var communalWidgetHost: CommunalWidgetHost
 
     private lateinit var appWidgetIdToRemove: MutableSharedFlow<Int>
 
@@ -78,6 +80,7 @@
         underTest =
             CommunalAppWidgetHostStartable(
                 appWidgetHost,
+                communalWidgetHost,
                 kosmos.communalInteractor,
                 kosmos.fakeUserTracker,
                 kosmos.applicationCoroutineScope,
@@ -143,16 +146,44 @@
         }
 
     @Test
+    fun observeHostWhenCommunalIsAvailable() =
+        with(kosmos) {
+            testScope.runTest {
+                setCommunalAvailable(true)
+                communalInteractor.setEditModeOpen(false)
+                verify(communalWidgetHost, never()).startObservingHost()
+                verify(communalWidgetHost, never()).stopObservingHost()
+
+                underTest.start()
+                runCurrent()
+
+                verify(communalWidgetHost).startObservingHost()
+                verify(communalWidgetHost, never()).stopObservingHost()
+
+                setCommunalAvailable(false)
+                runCurrent()
+
+                verify(communalWidgetHost).stopObservingHost()
+            }
+        }
+
+    @Test
     fun removeAppWidgetReportedByHost() =
         with(kosmos) {
             testScope.runTest {
                 // Set up communal widgets
                 val widget1 =
-                    mock<CommunalWidgetContentModel> { whenever(this.appWidgetId).thenReturn(1) }
+                    mock<CommunalWidgetContentModel.Available> {
+                        whenever(this.appWidgetId).thenReturn(1)
+                    }
                 val widget2 =
-                    mock<CommunalWidgetContentModel> { whenever(this.appWidgetId).thenReturn(2) }
+                    mock<CommunalWidgetContentModel.Available> {
+                        whenever(this.appWidgetId).thenReturn(2)
+                    }
                 val widget3 =
-                    mock<CommunalWidgetContentModel> { whenever(this.appWidgetId).thenReturn(3) }
+                    mock<CommunalWidgetContentModel.Available> {
+                        whenever(this.appWidgetId).thenReturn(3)
+                    }
                 fakeCommunalWidgetRepository.setCommunalWidgets(listOf(widget1, widget2, widget3))
 
                 underTest.start()
@@ -184,8 +215,9 @@
                     userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK),
                     selectedUserIndex = 0,
                 )
+                // One work widget, one pending work widget, and one personal widget.
                 val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
-                val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
+                val widget2 = createPendingWidgetForUser(2, USER_INFO_WORK.id)
                 val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
                 val widgets = listOf(widget1, widget2, widget3)
                 fakeCommunalWidgetRepository.setCommunalWidgets(widgets)
@@ -209,8 +241,8 @@
                 fakeKeyguardRepository.setKeyguardShowing(true)
                 runCurrent()
 
-                // Widget created for work profile is removed.
-                assertThat(communalWidgets).containsExactly(widget2, widget3)
+                // Both work widgets are removed.
+                assertThat(communalWidgets).containsExactly(widget3)
             }
         }
 
@@ -227,14 +259,32 @@
             )
         }
 
-    private fun createWidgetForUser(appWidgetId: Int, userId: Int): CommunalWidgetContentModel =
-        mock<CommunalWidgetContentModel> {
+    private fun createWidgetForUser(
+        appWidgetId: Int,
+        userId: Int
+    ): CommunalWidgetContentModel.Available =
+        mock<CommunalWidgetContentModel.Available> {
             whenever(this.appWidgetId).thenReturn(appWidgetId)
             val providerInfo = mock<AppWidgetProviderInfo>()
             whenever(providerInfo.profile).thenReturn(UserHandle(userId))
             whenever(this.providerInfo).thenReturn(providerInfo)
         }
 
+    private fun createPendingWidgetForUser(
+        appWidgetId: Int,
+        userId: Int,
+        priority: Int = 0,
+        packageName: String = "",
+        icon: Bitmap? = null,
+    ): CommunalWidgetContentModel.Pending =
+        CommunalWidgetContentModel.Pending(
+            appWidgetId = appWidgetId,
+            priority = priority,
+            packageName = packageName,
+            icon = icon,
+            user = UserHandle(userId),
+        )
+
     private companion object {
         val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
         val USER_INFO_WORK = UserInfo(10, "work", UserInfo.FLAG_PROFILE)
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 88f5e1b..054e516 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal.widgets
 
+import android.appwidget.AppWidgetHost
 import android.appwidget.AppWidgetManager
 import android.appwidget.AppWidgetProviderInfo
 import android.content.ComponentName
@@ -26,6 +27,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 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.log.logcatLogBuffer
 import com.android.systemui.testKosmos
@@ -40,6 +43,7 @@
 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
@@ -47,6 +51,8 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -59,6 +65,10 @@
 
     @Mock private lateinit var appWidgetManager: AppWidgetManager
     @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
+    @Mock private lateinit var providerInfo1: AppWidgetProviderInfo
+    @Mock private lateinit var providerInfo2: AppWidgetProviderInfo
+    @Mock private lateinit var providerInfo3: AppWidgetProviderInfo
+
     private val selectedUserInteractor: SelectedUserInteractor by lazy {
         kosmos.selectedUserInteractor
     }
@@ -69,8 +79,19 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        whenever(
+                appWidgetManager.bindAppWidgetIdIfAllowed(
+                    any<Int>(),
+                    any<UserHandle>(),
+                    any<ComponentName>(),
+                    any<Bundle>()
+                )
+            )
+            .thenReturn(true)
+
         underTest =
             CommunalWidgetHost(
+                kosmos.applicationCoroutineScope,
                 Optional.of(appWidgetManager),
                 appWidgetHost,
                 selectedUserInteractor,
@@ -89,15 +110,6 @@
 
             val user = UserHandle(checkNotNull(userId))
             whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(widgetId)
-            whenever(
-                    appWidgetManager.bindAppWidgetIdIfAllowed(
-                        any<Int>(),
-                        any<UserHandle>(),
-                        any<ComponentName>(),
-                        any<Bundle>(),
-                    )
-                )
-                .thenReturn(true)
 
             // bind the widget with the current user when no user is explicitly set
             val result = underTest.allocateIdAndBindWidget(provider)
@@ -121,15 +133,6 @@
             val user = UserHandle(0)
 
             whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(widgetId)
-            whenever(
-                    appWidgetManager.bindAppWidgetIdIfAllowed(
-                        any<Int>(),
-                        any<UserHandle>(),
-                        any<ComponentName>(),
-                        any<Bundle>()
-                    )
-                )
-                .thenReturn(true)
 
             // provider and user handle are both set
             val result = underTest.allocateIdAndBindWidget(provider, user)
@@ -172,6 +175,261 @@
             assertThat(result).isNull()
         }
 
+    @Test
+    fun listener_exactlyOneListenerRegisteredForEachWidgetWhenHostStartListening() =
+        testScope.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())
+
+            // Host starts listening
+            val observer =
+                withArgCaptor<CommunalAppWidgetHost.Observer> {
+                    verify(appWidgetHost).addObserver(capture())
+                }
+            observer.onHostStartListening()
+            runCurrent()
+
+            // Verify a listener is set for each widget
+            verify(appWidgetHost, times(3)).setListener(any(), any())
+            verify(appWidgetHost).setListener(eq(1), any())
+            verify(appWidgetHost).setListener(eq(2), any())
+            verify(appWidgetHost).setListener(eq(3), any())
+        }
+
+    @Test
+    fun listener_listenersRemovedWhenHostStopListening() =
+        testScope.runTest {
+            // 3 widgets registered with the host
+            whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2, 3))
+
+            underTest.startObservingHost()
+            runCurrent()
+
+            // Host starts listening
+            val observer =
+                withArgCaptor<CommunalAppWidgetHost.Observer> {
+                    verify(appWidgetHost).addObserver(capture())
+                }
+            observer.onHostStartListening()
+            runCurrent()
+
+            // Verify none of the listener is removed before host stop listening
+            verify(appWidgetHost, never()).removeListener(any())
+
+            observer.onHostStopListening()
+
+            // Verify each listener is removed
+            verify(appWidgetHost, times(3)).removeListener(any())
+            verify(appWidgetHost).removeListener(eq(1))
+            verify(appWidgetHost).removeListener(eq(2))
+            verify(appWidgetHost).removeListener(eq(3))
+        }
+
+    @Test
+    fun listener_addNewListenerWhenNewIdAllocated() =
+        testScope.runTest {
+            whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf())
+            val observer = start()
+
+            // Verify no listener is set before a new app widget id is allocated
+            verify(appWidgetHost, never()).setListener(any(), any())
+
+            // Allocate an app widget id
+            observer.onAllocateAppWidgetId(1)
+
+            // Verify new listener set for that app widget id
+            verify(appWidgetHost).setListener(eq(1), any())
+        }
+
+    @Test
+    fun listener_removeListenerWhenWidgetDeleted() =
+        testScope.runTest {
+            whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1))
+            val observer = start()
+
+            // Verify listener not removed before widget deleted
+            verify(appWidgetHost, never()).removeListener(eq(1))
+
+            // Widget deleted
+            observer.onDeleteAppWidgetId(1)
+
+            // Verify listener removed for that widget
+            verify(appWidgetHost).removeListener(eq(1))
+        }
+
+    @Test
+    fun providerInfo_populatesWhenStartListening() =
+        testScope.runTest {
+            whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2))
+            whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfo1)
+            whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfo2)
+
+            val providerInfoValues by collectValues(underTest.appWidgetProviders)
+
+            // Assert that the map is empty before host starts listening
+            assertThat(providerInfoValues).hasSize(1)
+            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
+            assertThat(providerInfoValues).hasSize(2)
+            assertThat(providerInfoValues[1])
+                .containsExactlyEntriesIn(
+                    mapOf(
+                        Pair(1, providerInfo1),
+                        Pair(2, providerInfo2),
+                    )
+                )
+        }
+
+    @Test
+    fun providerInfo_clearsWhenStopListening() =
+        testScope.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)
+            assertThat(providerInfo)
+                .containsExactlyEntriesIn(
+                    mapOf(
+                        Pair(1, providerInfo1),
+                        Pair(2, providerInfo2),
+                    )
+                )
+
+            // Host stop listening
+            observer.onHostStopListening()
+
+            // Assert that the provider info map is cleared
+            assertThat(providerInfo).isEmpty()
+        }
+
+    @Test
+    fun providerInfo_onUpdate() =
+        testScope.runTest {
+            whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2))
+            whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfo1)
+            whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfo2)
+
+            val providerInfo by collectLastValue(underTest.appWidgetProviders)
+
+            start()
+            runCurrent()
+
+            // Assert that the provider info map is populated
+            assertThat(providerInfo)
+                .containsExactlyEntriesIn(
+                    mapOf(
+                        Pair(1, providerInfo1),
+                        Pair(2, providerInfo2),
+                    )
+                )
+
+            // Provider info for widget 1 updated
+            val listener =
+                withArgCaptor<AppWidgetHost.AppWidgetHostListener> {
+                    verify(appWidgetHost).setListener(eq(1), capture())
+                }
+            listener.onUpdateProviderInfo(providerInfo3)
+            runCurrent()
+
+            // Assert that the update is reflected in the flow
+            assertThat(providerInfo)
+                .containsExactlyEntriesIn(
+                    mapOf(
+                        Pair(1, providerInfo3),
+                        Pair(2, providerInfo2),
+                    )
+                )
+        }
+
+    @Test
+    fun providerInfo_updateWhenANewWidgetIsBound() =
+        testScope.runTest {
+            whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2))
+            whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfo1)
+            whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfo2)
+
+            val providerInfo by collectLastValue(underTest.appWidgetProviders)
+
+            start()
+            runCurrent()
+
+            // Assert that the provider info map is populated
+            assertThat(providerInfo)
+                .containsExactlyEntriesIn(
+                    mapOf(
+                        Pair(1, providerInfo1),
+                        Pair(2, providerInfo2),
+                    )
+                )
+
+            // Bind a new widget
+            whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(3)
+            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)
+                .containsExactlyEntriesIn(
+                    mapOf(
+                        Pair(1, providerInfo1),
+                        Pair(2, providerInfo2),
+                        Pair(3, providerInfo3),
+                    )
+                )
+        }
+
+    @Test
+    fun providerInfo_updateWhenWidgetRemoved() =
+        testScope.runTest {
+            whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2))
+            whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfo1)
+            whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfo2)
+
+            val providerInfo by collectLastValue(underTest.appWidgetProviders)
+
+            val observer = start()
+            runCurrent()
+
+            // Assert that the provider info map is populated
+            assertThat(providerInfo)
+                .containsExactlyEntriesIn(
+                    mapOf(
+                        Pair(1, providerInfo1),
+                        Pair(2, providerInfo2),
+                    )
+                )
+
+            // Remove widget 1
+            observer.onDeleteAppWidgetId(1)
+            runCurrent()
+
+            // Assert that provider info for widget 1 is removed
+            assertThat(providerInfo)
+                .containsExactlyEntriesIn(
+                    mapOf(
+                        Pair(2, providerInfo2),
+                    )
+                )
+        }
+
     private fun selectUser() {
         kosmos.fakeUserRepository.selectedUser.value =
             SelectedUserModel(
@@ -179,4 +437,16 @@
                 selectionStatus = SelectionStatus.SELECTION_COMPLETE
             )
     }
+
+    private fun TestScope.start(): CommunalAppWidgetHost.Observer {
+        underTest.startObservingHost()
+        runCurrent()
+
+        val observer =
+            withArgCaptor<CommunalAppWidgetHost.Observer> {
+                verify(appWidgetHost).addObserver(capture())
+            }
+        observer.onHostStartListening()
+        return observer
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 20beabb..2546f27 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -41,6 +41,7 @@
 import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
 import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.concurrency.fakeExecutor
 import com.android.systemui.coroutines.FlowValue
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
@@ -144,6 +145,7 @@
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     private val testScope = kosmos.testScope
     private val fakeUserRepository = kosmos.fakeUserRepository
+    private val fakeExecutor = kosmos.fakeExecutor
     private lateinit var authStatus: FlowValue<FaceAuthenticationStatus?>
     private lateinit var detectStatus: FlowValue<FaceDetectionStatus?>
     private lateinit var authRunning: FlowValue<Boolean?>
@@ -220,12 +222,12 @@
             testScope.backgroundScope,
             testDispatcher,
             testDispatcher,
+            fakeExecutor,
             sessionTracker,
             uiEventLogger,
             FaceAuthenticationLogger(logcatLogBuffer("DeviceEntryFaceAuthRepositoryLog")),
             biometricSettingsRepository,
             deviceEntryFingerprintAuthRepository,
-            trustRepository,
             keyguardRepository,
             powerInteractor,
             keyguardInteractor,
@@ -292,6 +294,7 @@
     fun faceLockoutStatusIsPropagated() =
         testScope.runTest {
             initCollectors()
+            fakeExecutor.runAllReady()
             verify(faceManager).addLockoutResetCallback(faceLockoutResetCallback.capture())
             allPreconditionsToRunFaceAuthAreTrue()
 
@@ -1177,6 +1180,7 @@
     }
 
     private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue() {
+        fakeExecutor.runAllReady()
         verify(faceManager, atLeastOnce())
             .addLockoutResetCallback(faceLockoutResetCallback.capture())
         trustRepository.setCurrentUserTrusted(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 2b3f40f..f5c86e0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -27,11 +27,15 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.DreamManager;
 import android.content.res.Resources;
 import android.graphics.Region;
 import android.os.Handler;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.AttachedSurfaceControl;
+import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
 import android.view.ViewTreeObserver;
@@ -41,12 +45,15 @@
 
 import com.android.dream.lowlight.LowLightTransitionCoordinator;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.complication.ComplicationHostViewController;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.BlurUtils;
 
 import kotlinx.coroutines.CoroutineDispatcher;
@@ -91,6 +98,9 @@
     ViewGroup mDreamOverlayContentView;
 
     @Mock
+    View mHubGestureIndicatorView;
+
+    @Mock
     Handler mHandler;
 
     @Mock
@@ -115,6 +125,12 @@
     DreamOverlayStateController mStateController;
     @Mock
     KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    @Mock
+    ShadeInteractor mShadeInteractor;
+    @Mock
+    CommunalInteractor mCommunalInteractor;
+    @Mock
+    private DreamManager mDreamManager;
 
     DreamOverlayContainerViewController mController;
 
@@ -133,6 +149,7 @@
                 mDreamOverlayContainerView,
                 mComplicationHostViewController,
                 mDreamOverlayContentView,
+                mHubGestureIndicatorView,
                 mDreamOverlayStatusBarViewController,
                 mLowLightTransitionCoordinator,
                 mBlurUtils,
@@ -146,7 +163,22 @@
                 mAnimationsController,
                 mStateController,
                 mBouncerlessScrimController,
-                mKeyguardTransitionInteractor);
+                mKeyguardTransitionInteractor,
+                mShadeInteractor,
+                mCommunalInteractor,
+                mDreamManager);
+    }
+
+    @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+    @Test
+    public void testHubGestureIndicatorGoneWhenFlagOff() {
+        verify(mHubGestureIndicatorView, never()).setVisibility(View.VISIBLE);
+    }
+
+    @EnableFlags({Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_GLANCEABLE_HUB_GESTURE_HANDLE})
+    @Test
+    public void testHubGestureIndicatorVisibleWhenFlagOn() {
+        verify(mHubGestureIndicatorView).setVisibility(View.VISIBLE);
     }
 
     @Test
@@ -170,7 +202,7 @@
     @Test
     public void testBurnInProtectionStopsWhenContentViewDetached() {
         mController.onViewDetached();
-        verify(mHandler).removeCallbacks(any(Runnable.class));
+        verify(mHandler).removeCallbacksAndMessages(null);
     }
 
     @Test
@@ -281,4 +313,16 @@
 
         verify(mAnimationsController).cancelAnimations();
     }
+
+    @Test
+    public void onViewAttached_addsScrimExpansionCallback() {
+        mController.onViewAttached();
+        verify(mBouncerlessScrimController).addCallback(any());
+    }
+
+    @Test
+    public void onViewDetached_removesScrimExpansionCallback() {
+        mController.onViewDetached();
+        verify(mBouncerlessScrimController).removeCallback(any());
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
index feb7298..7292985 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
@@ -33,7 +33,6 @@
 import com.android.systemui.controls.panels.authorizedPanelsRepository
 import com.android.systemui.controls.panels.selectedComponentRepository
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
 import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor.Companion.MAX_UPDATE_CORRELATION_DELAY
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.settings.fakeUserTracker
@@ -64,7 +63,7 @@
 
     private val kosmos = testKosmos()
 
-    private lateinit var underTest: HomeControlsComponentInteractor
+    private val underTest by lazy { kosmos.homeControlsComponentInteractor }
 
     @Before
     fun setUp() =
@@ -73,8 +72,7 @@
             fakeUserRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
             whenever(controlsComponent.getControlsListingController())
                 .thenReturn(Optional.of(controlsListingController))
-
-            underTest = homeControlsComponentInteractor
+            Unit
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
index e3c6dee..29fbee0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
@@ -108,7 +108,7 @@
         mTouchHandler.onSessionStart(mTouchSession);
         verify(mTouchSession).registerInputListener(inputEventListenerArgumentCaptor.capture());
         inputEventListenerArgumentCaptor.getValue().onInputEvent(motionEvent);
-        verify(mCentralSurfaces).handleExternalShadeWindowTouch(motionEvent);
+        verify(mCentralSurfaces).handleDreamTouch(motionEvent);
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/FingerprintPropertyRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/FingerprintPropertyRepositoryTest.kt
new file mode 100644
index 0000000..1e7ed63
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/FingerprintPropertyRepositoryTest.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.repository
+
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepositoryImpl
+import com.android.systemui.coroutines.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+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.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@ExperimentalCoroutinesApi
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class FingerprintPropertyRepositoryTest : SysuiTestCase() {
+    @JvmField @Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+    private val testScope = TestScope()
+    private lateinit var underTest: FingerprintPropertyRepositoryImpl
+    @Mock private lateinit var fingerprintManager: FingerprintManager
+    @Captor
+    private lateinit var fingerprintCallbackCaptor:
+        ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback>
+
+    @Before
+    fun setup() {
+        underTest =
+            FingerprintPropertyRepositoryImpl(
+                testScope.backgroundScope,
+                Dispatchers.Main.immediate,
+                fingerprintManager,
+            )
+    }
+
+    @Test
+    fun propertiesInitialized_onStartFalse() =
+        testScope.runTest {
+            val propertiesInitialized by collectLastValue(underTest.propertiesInitialized)
+            assertThat(propertiesInitialized).isFalse()
+        }
+
+    @Test
+    fun propertiesInitialized_onStartTrue() =
+        testScope.runTest {
+            //            // collect sensorType to update fingerprintCallback before
+            // propertiesInitialized
+            //            // is listened for
+            val sensorType by collectLastValue(underTest.sensorType)
+            runCurrent()
+            captureFingerprintCallback()
+
+            fingerprintCallbackCaptor.value.onAllAuthenticatorsRegistered(emptyList())
+            val propertiesInitialized by collectLastValue(underTest.propertiesInitialized)
+            assertThat(propertiesInitialized).isTrue()
+        }
+
+    @Test
+    fun propertiesInitialized_updatedToTrue() =
+        testScope.runTest {
+            val propertiesInitialized by collectLastValue(underTest.propertiesInitialized)
+            assertThat(propertiesInitialized).isFalse()
+
+            captureFingerprintCallback()
+            fingerprintCallbackCaptor.value.onAllAuthenticatorsRegistered(emptyList())
+            assertThat(propertiesInitialized).isTrue()
+        }
+
+    private fun captureFingerprintCallback() {
+        verify(fingerprintManager)
+            .addAuthenticatorsRegisteredCallback(fingerprintCallbackCaptor.capture())
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index cb2d4e0..addbdb6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -60,17 +60,19 @@
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val repository = kosmos.fakeKeyguardRepository
-    private val sceneInteractor = kosmos.sceneInteractor
-    private val fromGoneTransitionInteractor = kosmos.fromGoneTransitionInteractor
-    private val commandQueue = kosmos.fakeCommandQueue
-    private val configRepository = kosmos.fakeConfigurationRepository
-    private val bouncerRepository = kosmos.keyguardBouncerRepository
-    private val shadeRepository = kosmos.shadeRepository
-    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val repository by lazy { kosmos.fakeKeyguardRepository }
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor }
+    private val commandQueue by lazy { kosmos.fakeCommandQueue }
+    private val configRepository by lazy { kosmos.fakeConfigurationRepository }
+    private val bouncerRepository by lazy { kosmos.keyguardBouncerRepository }
+    private val shadeRepository by lazy { kosmos.shadeRepository }
+    private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+
     private val transitionState: MutableStateFlow<ObservableTransitionState> =
         MutableStateFlow(ObservableTransitionState.Idle(Scenes.Gone))
-    private val underTest = kosmos.keyguardInteractor
+
+    private val underTest by lazy { kosmos.keyguardInteractor }
 
     @Before
     fun setUp() {
@@ -275,6 +277,28 @@
         }
 
     @Test
+    fun keyguardTranslationY_whenNotGoneAndShadeIsReesetEmitsZero() =
+        testScope.runTest {
+            val keyguardTranslationY by collectLastValue(underTest.keyguardTranslationY)
+
+            configRepository.setDimensionPixelSize(
+                R.dimen.keyguard_translate_distance_on_swipe_up,
+                100
+            )
+            configRepository.onAnyConfigurationChange()
+
+            shadeRepository.setLegacyShadeExpansion(1f)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.LOCKSCREEN,
+                testScope,
+            )
+
+            assertThat(keyguardTranslationY).isEqualTo(0f)
+        }
+
+    @Test
     fun keyguardTranslationY_whenTransitioningToGoneAndShadeIsExpandingEmitsNonZero() =
         testScope.runTest {
             val keyguardTranslationY by collectLastValue(underTest.keyguardTranslationY)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 41229255..99cccb2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -19,9 +19,13 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
@@ -29,36 +33,74 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED
 import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
 import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertEquals
 import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertThrows
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @kotlinx.coroutines.ExperimentalCoroutinesApi
-@android.platform.test.annotations.EnabledOnRavenwood
 class KeyguardTransitionInteractorTest : SysuiTestCase() {
     val kosmos = testKosmos()
     val underTest = kosmos.keyguardTransitionInteractor
     val repository = kosmos.fakeKeyguardTransitionRepository
     val testScope = kosmos.testScope
 
+    private val sceneTransitions =
+        MutableStateFlow<ObservableTransitionState>(
+            ObservableTransitionState.Idle(Scenes.Lockscreen)
+        )
+
+    private val lsToGone =
+        ObservableTransitionState.Transition(
+            Scenes.Lockscreen,
+            Scenes.Gone,
+            flowOf(Scenes.Lockscreen),
+            flowOf(0f),
+            false,
+            flowOf(false)
+        )
+
+    private val goneToLs =
+        ObservableTransitionState.Transition(
+            Scenes.Gone,
+            Scenes.Lockscreen,
+            flowOf(Scenes.Lockscreen),
+            flowOf(0f),
+            false,
+            flowOf(false)
+        )
+
+    @Before
+    fun setUp() {
+        kosmos.sceneContainerRepository.setTransitionState(sceneTransitions)
+    }
+
     @Test
     fun transitionCollectorsReceivesOnlyAppropriateEvents() =
         testScope.runTest {
-            val lockscreenToAodSteps by collectValues(underTest.transition(LOCKSCREEN, AOD))
-            val aodToLockscreenSteps by collectValues(underTest.transition(AOD, LOCKSCREEN))
+            val lockscreenToAodSteps by
+                collectValues(underTest.transition(Edge.create(LOCKSCREEN, AOD)))
+            val aodToLockscreenSteps by
+                collectValues(underTest.transition(Edge.create(AOD, LOCKSCREEN)))
 
             val steps = mutableListOf<TransitionStep>()
             steps.add(TransitionStep(AOD, GONE, 0f, STARTED))
@@ -235,7 +277,13 @@
                 .isEqualTo(
                     listOf(
                         // The initial transition will also get sent when collect started
-                        TransitionStep(OFF, LOCKSCREEN, 0f, STARTED),
+                        TransitionStep(
+                            OFF,
+                            LOCKSCREEN,
+                            0f,
+                            STARTED,
+                            ownerName = "KeyguardTransitionRepository(boot)"
+                        ),
                         steps[0],
                         steps[3],
                         steps[6]
@@ -476,6 +524,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun isInTransitionToState() =
         testScope.runTest {
             val results by collectValues(underTest.isInTransitionToState(GONE))
@@ -580,7 +629,7 @@
                 )
 
             sendSteps(
-                TransitionStep(DOZING, GONE, 0f, STARTED),
+                TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
             )
 
             assertThat(results)
@@ -592,7 +641,7 @@
                 )
 
             sendSteps(
-                TransitionStep(DOZING, GONE, 0f, RUNNING),
+                TransitionStep(DOZING, LOCKSCREEN, 0f, RUNNING),
             )
 
             assertThat(results)
@@ -604,7 +653,7 @@
                 )
 
             sendSteps(
-                TransitionStep(DOZING, GONE, 0f, FINISHED),
+                TransitionStep(DOZING, LOCKSCREEN, 0f, FINISHED),
             )
 
             assertThat(results)
@@ -617,9 +666,9 @@
                 )
 
             sendSteps(
-                TransitionStep(GONE, DOZING, 0f, STARTED),
-                TransitionStep(GONE, DOZING, 0f, RUNNING),
-                TransitionStep(GONE, DOZING, 1f, FINISHED),
+                TransitionStep(LOCKSCREEN, DOZING, 0f, STARTED),
+                TransitionStep(LOCKSCREEN, DOZING, 0f, RUNNING),
+                TransitionStep(LOCKSCREEN, DOZING, 1f, FINISHED),
             )
 
             assertThat(results)
@@ -632,8 +681,8 @@
                 )
 
             sendSteps(
-                TransitionStep(DOZING, GONE, 0f, STARTED),
-                TransitionStep(DOZING, GONE, 0f, RUNNING),
+                TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
+                TransitionStep(DOZING, LOCKSCREEN, 0f, RUNNING),
             )
 
             assertThat(results)
@@ -1398,6 +1447,143 @@
             )
         }
 
+    @Test
+    @DisableSceneContainer
+    fun transition_no_conversion_with_flag_off() =
+        testScope.runTest {
+            val currentStates by
+                collectValues(underTest.transition(Edge.create(PRIMARY_BOUNCER, GONE)))
+
+            val sendStep1 = TransitionStep(PRIMARY_BOUNCER, GONE, 0f, STARTED)
+            sendSteps(sendStep1)
+
+            assertEquals(listOf(sendStep1), currentStates)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun transition_conversion_with_flag_on() =
+        testScope.runTest {
+            val currentStates by
+                collectValues(underTest.transition(Edge.create(PRIMARY_BOUNCER, GONE)))
+
+            val sendStep1 = TransitionStep(PRIMARY_BOUNCER, GONE, 0f, STARTED)
+            sendSteps(sendStep1)
+
+            assertEquals(listOf<TransitionStep>(), currentStates)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun transition_conversion_emits_values_with_sceneContainer_in_correct_state() =
+        testScope.runTest {
+            val currentStates by collectValues(underTest.transition(Edge.create(LOCKSCREEN, GONE)))
+            val currentStatesConverted by
+                collectValues(underTest.transition(Edge.create(LOCKSCREEN, UNDEFINED)))
+
+            sceneTransitions.value = lsToGone
+            val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+            val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
+            val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)
+            sendSteps(sendStep1, sendStep2, sendStep3)
+
+            assertEquals(listOf(sendStep1, sendStep2), currentStates)
+            assertEquals(listOf(sendStep1, sendStep2), currentStatesConverted)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun transition_conversion_emits_nothing_with_sceneContainer_in_wrong_state() =
+        testScope.runTest {
+            val currentStates by collectValues(underTest.transition(Edge.create(LOCKSCREEN, GONE)))
+
+            sceneTransitions.value = goneToLs
+            val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+            val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
+            val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)
+            sendSteps(sendStep1, sendStep2, sendStep3)
+
+            assertEquals(listOf<TransitionStep>(), currentStates)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun transition_conversion_emits_values_when_edge_within_lockscreen_scene() =
+        testScope.runTest {
+            val currentStates by
+                collectValues(underTest.transition(Edge.create(LOCKSCREEN, DOZING)))
+
+            sceneTransitions.value = goneToLs
+            val sendStep1 = TransitionStep(LOCKSCREEN, DOZING, 0f, STARTED)
+            val sendStep2 = TransitionStep(LOCKSCREEN, DOZING, 1f, FINISHED)
+            val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)
+            sendSteps(sendStep1, sendStep2, sendStep3)
+
+            assertEquals(listOf(sendStep1, sendStep2), currentStates)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun transition_conversion_emits_values_with_null_edge_within_lockscreen_scene() =
+        testScope.runTest {
+            val currentStates by collectValues(underTest.transition(Edge.create(LOCKSCREEN, null)))
+            val currentStatesReversed by
+                collectValues(underTest.transition(Edge.create(null, LOCKSCREEN)))
+
+            sceneTransitions.value = goneToLs
+            val sendStep1 = TransitionStep(LOCKSCREEN, DOZING, 0f, STARTED)
+            val sendStep2 = TransitionStep(LOCKSCREEN, DOZING, 1f, FINISHED)
+            val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)
+            val sendStep4 = TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)
+            sendSteps(sendStep1, sendStep2, sendStep3, sendStep4)
+
+            assertEquals(listOf(sendStep1, sendStep2, sendStep3), currentStates)
+            assertEquals(listOf(sendStep4), currentStatesReversed)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun transition_conversion_emits_values_with_null_edge_out_of_lockscreen_scene() =
+        testScope.runTest {
+            val currentStates by collectValues(underTest.transition(Edge.create(null, UNDEFINED)))
+            val currentStatesMapped by collectValues(underTest.transition(Edge.create(null, GONE)))
+
+            sceneTransitions.value = lsToGone
+            val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+            val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
+            val sendStep3 = TransitionStep(UNDEFINED, AOD, 0f, STARTED)
+            val sendStep4 = TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)
+            sendSteps(sendStep1, sendStep2, sendStep3, sendStep4)
+
+            assertEquals(listOf(sendStep1, sendStep2), currentStates)
+            assertEquals(listOf(sendStep1, sendStep2), currentStatesMapped)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun transition_conversion_does_not_emit_with_null_edge_with_wrong_stl_state() =
+        testScope.runTest {
+            val currentStatesMapped by collectValues(underTest.transition(Edge.create(null, GONE)))
+
+            sceneTransitions.value = goneToLs
+            val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+            val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
+            val sendStep3 = TransitionStep(UNDEFINED, AOD, 0f, STARTED)
+            val sendStep4 = TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)
+            sendSteps(sendStep1, sendStep2, sendStep3, sendStep4)
+
+            assertEquals(listOf<TransitionStep>(), currentStatesMapped)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun transition_null_edges_throw() =
+        testScope.runTest {
+            assertThrows(IllegalStateException::class.java) {
+                underTest.transition(Edge.create(null, null))
+            }
+        }
+
     private suspend fun sendSteps(vararg steps: TransitionStep) {
         steps.forEach {
             repository.sendTransitionStep(it)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
index 3b6f6a2..f31eb7f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -58,7 +58,7 @@
 @ExperimentalCoroutinesApi
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class UdfpsKeyguardInteractorTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class UdfpsKeyguardInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
     val kosmos = testKosmos()
     val testScope = kosmos.testScope
     val keyguardRepository = kosmos.fakeKeyguardRepository
@@ -88,7 +88,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
index 0ac7ff5..a0fed6b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
@@ -23,11 +23,13 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.milliseconds
@@ -50,11 +52,14 @@
     @Before
     fun setUp() {
         underTest =
-            animationFlow.setup(
-                duration = 1000.milliseconds,
-                from = GONE,
-                to = DREAMING,
-            )
+            animationFlow
+                .setup(
+                    duration = 1000.milliseconds,
+                    edge = Edge.create(from = Scenes.Gone, to = DREAMING),
+                )
+                .setupWithoutSceneContainer(
+                    edge = Edge.create(from = GONE, to = DREAMING),
+                )
     }
 
     @Test(expected = IllegalArgumentException::class)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index e270d9e..519bb6e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -62,8 +62,8 @@
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     private val keyguardClockRepository = kosmos.fakeKeyguardClockRepository
     private lateinit var underTest: AodBurnInViewModel
-
-    private var burnInParameters = BurnInParameters()
+    // assign a smaller value to minViewY to avoid overflow
+    private var burnInParameters = BurnInParameters(minViewY = Int.MAX_VALUE / 2)
     private val burnInFlow = MutableStateFlow(BurnInModel())
 
     @Before
@@ -296,52 +296,111 @@
                     scale = 0.5f,
                 )
 
-            assertThat(movement?.translationX).isEqualTo(0)
-            assertThat(movement?.translationY).isEqualTo(0)
+            assertThat(movement?.translationX).isEqualTo(20)
+            assertThat(movement?.translationY).isEqualTo(30)
             assertThat(movement?.scale).isEqualTo(0.5f)
             assertThat(movement?.scaleClockOnly).isEqualTo(false)
         }
 
     @Test
-    fun translationAndScale_composeFlagOn_weatherLargeClock() =
-        testBurnInViewModelWhenComposeFlagOn(
+    fun translationAndScale_composeFlagOff_weatherLargeClock() =
+        testBurnInViewModelForClocks(
             isSmallClock = false,
             isWeatherClock = true,
-            expectedScaleOnly = false
+            expectedScaleOnly = false,
+            enableMigrateClocksToBlueprintFlag = true,
+            enableComposeLockscreenFlag = false
+        )
+
+    @Test
+    fun translationAndScale_composeFlagOff_weatherSmallClock() =
+        testBurnInViewModelForClocks(
+            isSmallClock = true,
+            isWeatherClock = true,
+            expectedScaleOnly = false,
+            enableMigrateClocksToBlueprintFlag = true,
+            enableComposeLockscreenFlag = false
+        )
+
+    @Test
+    fun translationAndScale_composeFlagOff_nonWeatherLargeClock() =
+        testBurnInViewModelForClocks(
+            isSmallClock = false,
+            isWeatherClock = false,
+            expectedScaleOnly = true,
+            enableMigrateClocksToBlueprintFlag = true,
+            enableComposeLockscreenFlag = false
+        )
+
+    @Test
+    fun translationAndScale_composeFlagOff_nonWeatherSmallClock() =
+        testBurnInViewModelForClocks(
+            isSmallClock = true,
+            isWeatherClock = false,
+            expectedScaleOnly = false,
+            enableMigrateClocksToBlueprintFlag = true,
+            enableComposeLockscreenFlag = false
+        )
+
+    @Test
+    fun translationAndScale_composeFlagOn_weatherLargeClock() =
+        testBurnInViewModelForClocks(
+            isSmallClock = false,
+            isWeatherClock = true,
+            expectedScaleOnly = false,
+            enableMigrateClocksToBlueprintFlag = true,
+            enableComposeLockscreenFlag = true
         )
 
     @Test
     fun translationAndScale_composeFlagOn_weatherSmallClock() =
-        testBurnInViewModelWhenComposeFlagOn(
+        testBurnInViewModelForClocks(
             isSmallClock = true,
             isWeatherClock = true,
-            expectedScaleOnly = true
+            expectedScaleOnly = false,
+            enableMigrateClocksToBlueprintFlag = true,
+            enableComposeLockscreenFlag = true
         )
 
     @Test
     fun translationAndScale_composeFlagOn_nonWeatherLargeClock() =
-        testBurnInViewModelWhenComposeFlagOn(
+        testBurnInViewModelForClocks(
             isSmallClock = false,
             isWeatherClock = false,
-            expectedScaleOnly = true
+            expectedScaleOnly = true,
+            enableMigrateClocksToBlueprintFlag = true,
+            enableComposeLockscreenFlag = true
         )
 
     @Test
     fun translationAndScale_composeFlagOn_nonWeatherSmallClock() =
-        testBurnInViewModelWhenComposeFlagOn(
+        testBurnInViewModelForClocks(
             isSmallClock = true,
             isWeatherClock = false,
-            expectedScaleOnly = true
+            expectedScaleOnly = false,
+            enableMigrateClocksToBlueprintFlag = true,
+            enableComposeLockscreenFlag = true
         )
 
-    private fun testBurnInViewModelWhenComposeFlagOn(
+    private fun testBurnInViewModelForClocks(
         isSmallClock: Boolean,
         isWeatherClock: Boolean,
-        expectedScaleOnly: Boolean
+        expectedScaleOnly: Boolean,
+        enableMigrateClocksToBlueprintFlag: Boolean,
+        enableComposeLockscreenFlag: Boolean
     ) =
         testScope.runTest {
-            mSetFlagsRule.enableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-            mSetFlagsRule.enableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN)
+            if (enableMigrateClocksToBlueprintFlag) {
+                mSetFlagsRule.enableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+            } else {
+                mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+            }
+
+            if (enableComposeLockscreenFlag) {
+                mSetFlagsRule.enableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN)
+            } else {
+                mSetFlagsRule.disableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN)
+            }
             if (isSmallClock) {
                 keyguardClockRepository.setClockSize(ClockSize.SMALL)
                 // we need the following step to update stateFlow value
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
index 31b67b4..cde703b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
@@ -16,36 +16,57 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.shadeTestUtil
 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
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @ExperimentalCoroutinesApi
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class AodToLockscreenTransitionViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class AodToLockscreenTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     val kosmos = testKosmos()
     val testScope = kosmos.testScope
     val repository = kosmos.fakeKeyguardTransitionRepository
-    val shadeRepository = kosmos.fakeShadeRepository
+    val shadeTestUtil by lazy { kosmos.shadeTestUtil }
     val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
-    val underTest = kosmos.aodToLockscreenTransitionViewModel
+    lateinit var underTest: AodToLockscreenTransitionViewModel
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
+    @Before
+    fun setup() {
+        underTest = kosmos.aodToLockscreenTransitionViewModel
+    }
 
     @Test
     fun deviceEntryParentViewShows() =
@@ -65,7 +86,7 @@
         testScope.runTest {
             val alpha by collectLastValue(underTest.notificationAlpha)
 
-            shadeRepository.setQsExpansion(0.5f)
+            shadeTestUtil.setQsExpansion(0.5f)
             runCurrent()
 
             repository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -81,7 +102,7 @@
         testScope.runTest {
             val alpha by collectLastValue(underTest.notificationAlpha)
 
-            shadeRepository.setQsExpansion(0f)
+            shadeTestUtil.setQsExpansion(0f)
             runCurrent()
 
             repository.sendTransitionStep(step(0f, TransitionState.STARTED))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
index fee18dd..7a9bd92 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
@@ -16,13 +16,15 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.domain.interactor.mockPrimaryBouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.BrokenWithSceneContainer
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -31,29 +33,25 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.statusbar.sysuiStatusBarStateController
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.milliseconds
-import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
 import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class BouncerToGoneFlowsTest : SysuiTestCase() {
-    @Mock private lateinit var shadeInteractor: ShadeInteractor
-
-    private val shadeExpansionStateFlow = MutableStateFlow(0.1f)
+@RunWith(ParameterizedAndroidJunit4::class)
+class BouncerToGoneFlowsTest(flags: FlagsParameterization) : SysuiTestCase() {
 
     private val kosmos =
         testKosmos().apply {
@@ -61,25 +59,41 @@
         }
     private val testScope = kosmos.testScope
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
-    private val shadeRepository = kosmos.shadeRepository
     private val sysuiStatusBarStateController = kosmos.sysuiStatusBarStateController
     private val primaryBouncerInteractor = kosmos.mockPrimaryBouncerInteractor
-    private val underTest = kosmos.bouncerToGoneFlows
+
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+    private lateinit var underTest: BouncerToGoneFlows
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
         sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(false)
+        underTest = kosmos.bouncerToGoneFlows
     }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun scrimAlpha_runDimissFromKeyguard_shadeExpanded() =
         testScope.runTest {
             val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
             runCurrent()
 
-            shadeRepository.setLockscreenShadeExpansion(1f)
+            shadeTestUtil.setLockscreenShadeExpansion(1f)
             whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
 
             keyguardTransitionRepository.sendTransitionSteps(
@@ -99,12 +113,13 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun scrimAlpha_runDimissFromKeyguard_shadeNotExpanded() =
         testScope.runTest {
             val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
             runCurrent()
 
-            shadeRepository.setLockscreenShadeExpansion(0f)
+            shadeTestUtil.setLockscreenShadeExpansion(0f)
 
             whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
 
@@ -123,6 +138,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun scrimBehindAlpha_leaveShadeOpen() =
         testScope.runTest {
             val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
@@ -147,6 +163,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun showAllNotifications_isTrue_whenLeaveShadeOpen() =
         testScope.runTest {
             val showAllNotifications by
@@ -163,6 +180,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun showAllNotifications_isFalse_whenLeaveShadeIsNotOpen() =
         testScope.runTest {
             val showAllNotifications by
@@ -179,6 +197,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(330311871)
     fun scrimBehindAlpha_doNotLeaveShadeOpen() =
         testScope.runTest {
             val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 2e1765a..20ffa33 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -30,6 +30,8 @@
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.parameterizeSceneContainerFlag
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -37,7 +39,9 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 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.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
 import com.android.systemui.statusbar.phone.dozeParameters
@@ -49,6 +53,8 @@
 import com.android.systemui.util.ui.value
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -60,7 +66,7 @@
 
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class KeyguardRootViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
@@ -75,7 +81,11 @@
 
     private val viewState = ViewStateAccessor()
 
-    // add to init block
+    private val transitionState =
+        MutableStateFlow<ObservableTransitionState>(
+            ObservableTransitionState.Idle(Scenes.Lockscreen)
+        )
+
     companion object {
         @JvmStatic
         @Parameters(name = "{0}")
@@ -85,7 +95,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
@@ -97,6 +107,7 @@
                 AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
             )
         }
+        kosmos.sceneContainerRepository.setTransitionState(transitionState)
     }
 
     @Test
@@ -310,6 +321,32 @@
         }
 
     @Test
+    @EnableSceneContainer
+    fun alpha_transitionToHub_isZero_scene_container() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.alpha(viewState))
+
+            transitionState.value =
+                ObservableTransitionState.Transition(
+                    fromScene = Scenes.Lockscreen,
+                    toScene = Scenes.Communal,
+                    emptyFlow(),
+                    emptyFlow(),
+                    false,
+                    emptyFlow()
+                )
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.UNDEFINED,
+                testScope,
+            )
+
+            assertThat(alpha).isEqualTo(0f)
+        }
+
+    @Test
+    @DisableSceneContainer
     fun alpha_transitionToHub_isZero() =
         testScope.runTest {
             val alpha by collectLastValue(underTest.alpha(viewState))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index ec2cb04..de4b999 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -47,7 +47,7 @@
 
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class LockscreenContentViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
 
     private val kosmos: Kosmos = testKosmos()
 
@@ -62,7 +62,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index bc9d257..f46ca00 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -24,6 +24,7 @@
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.TransitionKey
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -39,6 +40,7 @@
 import com.android.systemui.power.shared.model.WakefulnessState
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.TransitionKeys
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
@@ -117,6 +119,17 @@
             }
         }
 
+        private fun expectedDownTransitionKey(
+            isSingleShade: Boolean,
+            isShadeTouchable: Boolean,
+        ): TransitionKey? {
+            return when {
+                !isShadeTouchable -> null
+                !isSingleShade -> TransitionKeys.ToSplitShade
+                else -> null
+            }
+        }
+
         private fun expectedUpDestination(
             canSwipeToEnter: Boolean,
             isShadeTouchable: Boolean,
@@ -184,18 +197,16 @@
             )
 
             val destinationScenes by collectLastValue(underTest.destinationScenes)
-
-            assertThat(
-                    destinationScenes
-                        ?.get(
-                            Swipe(
-                                SwipeDirection.Down,
-                                fromSource = Edge.Top.takeIf { downFromEdge },
-                                pointerCount = if (downWithTwoPointers) 2 else 1,
-                            )
-                        )
-                        ?.toScene
+            val downDestination =
+                destinationScenes?.get(
+                    Swipe(
+                        SwipeDirection.Down,
+                        fromSource = Edge.Top.takeIf { downFromEdge },
+                        pointerCount = if (downWithTwoPointers) 2 else 1,
+                    )
                 )
+
+            assertThat(downDestination?.toScene)
                 .isEqualTo(
                     expectedDownDestination(
                         downFromEdge = downFromEdge,
@@ -204,6 +215,14 @@
                     )
                 )
 
+            assertThat(downDestination?.transitionKey)
+                .isEqualTo(
+                    expectedDownTransitionKey(
+                        isSingleShade = isSingleShade,
+                        isShadeTouchable = isShadeTouchable,
+                    )
+                )
+
             assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
                 .isEqualTo(
                     expectedUpDestination(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
index bef9515..bc381f2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
@@ -16,13 +16,14 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -32,31 +33,53 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.testKosmos
 import com.google.common.collect.Range
 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
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @ExperimentalCoroutinesApi
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class LockscreenToAodTransitionViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class LockscreenToAodTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
             fakeFeatureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) }
         }
     private val testScope = kosmos.testScope
     private val repository = kosmos.fakeKeyguardTransitionRepository
-    private val shadeRepository = kosmos.shadeRepository
     private val keyguardRepository = kosmos.fakeKeyguardRepository
     private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
     private val biometricSettingsRepository = kosmos.biometricSettingsRepository
-    private val underTest = kosmos.lockscreenToAodTransitionViewModel
+
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+    lateinit var underTest: LockscreenToAodTransitionViewModel
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
+    @Before
+    fun setup() {
+        underTest = kosmos.lockscreenToAodTransitionViewModel
+    }
 
     @Test
     fun backgroundViewAlpha_shadeNotExpanded() =
@@ -195,11 +218,11 @@
 
     private fun shadeExpanded(expanded: Boolean) {
         if (expanded) {
-            shadeRepository.setQsExpansion(1f)
+            shadeTestUtil.setQsExpansion(1f)
         } else {
             keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-            shadeRepository.setQsExpansion(0f)
-            shadeRepository.setLockscreenShadeExpansion(0f)
+            shadeTestUtil.setQsExpansion(0f)
+            shadeTestUtil.setLockscreenShadeExpansion(0f)
         }
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index 8f04ec38..9337793 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -18,24 +18,25 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.ShadeTestUtil
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.testKosmos
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
@@ -45,10 +46,12 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class LockscreenToDreamingTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
 
     private val kosmos =
         testKosmos().apply {
@@ -56,14 +59,27 @@
         }
     private val testScope = kosmos.testScope
     private lateinit var repository: FakeKeyguardTransitionRepository
-    private lateinit var shadeRepository: ShadeRepository
+    private lateinit var shadeTestUtil: ShadeTestUtil
     private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var underTest: LockscreenToDreamingTransitionViewModel
 
+    // add to init block
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
     @Before
     fun setUp() {
         repository = kosmos.fakeKeyguardTransitionRepository
-        shadeRepository = kosmos.shadeRepository
+        shadeTestUtil = kosmos.shadeTestUtil
         keyguardRepository = kosmos.fakeKeyguardRepository
         underTest = kosmos.lockscreenToDreamingTransitionViewModel
     }
@@ -177,11 +193,11 @@
 
     private fun shadeExpanded(expanded: Boolean) {
         if (expanded) {
-            shadeRepository.setQsExpansion(1f)
+            shadeTestUtil.setQsExpansion(1f)
         } else {
             keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-            shadeRepository.setQsExpansion(0f)
-            shadeRepository.setLockscreenShadeExpansion(0f)
+            shadeTestUtil.setQsExpansion(0f)
+            shadeTestUtil.setLockscreenShadeExpansion(0f)
         }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index b120f87..6ce7e88 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -18,27 +18,28 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
-import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.ShadeTestUtil
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.testKosmos
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
@@ -48,25 +49,40 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
             fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         }
     private val testScope = kosmos.testScope
     private lateinit var repository: FakeKeyguardTransitionRepository
-    private lateinit var shadeRepository: ShadeRepository
+    private lateinit var shadeTestUtil: ShadeTestUtil
     private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var configurationRepository: FakeConfigurationRepository
     private lateinit var underTest: LockscreenToOccludedTransitionViewModel
 
+    // add to init block
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
     @Before
     fun setUp() {
         repository = kosmos.fakeKeyguardTransitionRepository
-        shadeRepository = kosmos.shadeRepository
+        shadeTestUtil = kosmos.shadeTestUtil
         keyguardRepository = kosmos.fakeKeyguardRepository
         configurationRepository = kosmos.fakeConfigurationRepository
         underTest = kosmos.lockscreenToOccludedTransitionViewModel
@@ -200,11 +216,11 @@
 
     private fun shadeExpanded(expanded: Boolean) {
         if (expanded) {
-            shadeRepository.setQsExpansion(1f)
+            shadeTestUtil.setQsExpansion(1f)
         } else {
             keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-            shadeRepository.setQsExpansion(0f)
-            shadeRepository.setLockscreenShadeExpansion(0f)
+            shadeTestUtil.setQsExpansion(0f)
+            shadeTestUtil.setLockscreenShadeExpansion(0f)
         }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
index 43ab93a..1c1fcc4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
@@ -16,11 +16,14 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.BrokenWithSceneContainer
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -29,31 +32,63 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.testKosmos
 import com.google.common.collect.Range
 import com.google.common.truth.Truth
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @ExperimentalCoroutinesApi
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class LockscreenToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameterization) :
+    SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
             fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         }
     private val testScope = kosmos.testScope
     private val repository = kosmos.fakeKeyguardTransitionRepository
-    private val shadeRepository = kosmos.shadeRepository
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
     private val keyguardRepository = kosmos.fakeKeyguardRepository
-    private val underTest = kosmos.lockscreenToPrimaryBouncerTransitionViewModel
+    private lateinit var underTest: LockscreenToPrimaryBouncerTransitionViewModel
+
+    private val transitionState =
+        MutableStateFlow<ObservableTransitionState>(
+            ObservableTransitionState.Idle(Scenes.Lockscreen)
+        )
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
+    @Before
+    fun setup() {
+        underTest = kosmos.lockscreenToPrimaryBouncerTransitionViewModel
+    }
 
     @Test
+    @BrokenWithSceneContainer(330311871)
     fun deviceEntryParentViewAlpha_shadeExpanded() =
         testScope.runTest {
             val actual by collectLastValue(underTest.deviceEntryParentViewAlpha)
@@ -85,6 +120,17 @@
             shadeExpanded(false)
             runCurrent()
 
+            kosmos.sceneContainerRepository.setTransitionState(transitionState)
+            transitionState.value =
+                ObservableTransitionState.Transition(
+                    fromScene = Scenes.Lockscreen,
+                    toScene = Scenes.Bouncer,
+                    emptyFlow(),
+                    emptyFlow(),
+                    false,
+                    emptyFlow()
+                )
+            runCurrent()
             // fade out
             repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             runCurrent()
@@ -110,7 +156,9 @@
     ): TransitionStep {
         return TransitionStep(
             from = KeyguardState.LOCKSCREEN,
-            to = KeyguardState.PRIMARY_BOUNCER,
+            to =
+                if (SceneContainerFlag.isEnabled) KeyguardState.UNDEFINED
+                else KeyguardState.PRIMARY_BOUNCER,
             value = value,
             transitionState = state,
             ownerName = "LockscreenToPrimaryBouncerTransitionViewModelTest"
@@ -119,11 +167,11 @@
 
     private fun shadeExpanded(expanded: Boolean) {
         if (expanded) {
-            shadeRepository.setQsExpansion(1f)
+            shadeTestUtil.setQsExpansion(1f)
         } else {
             keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-            shadeRepository.setQsExpansion(0f)
-            shadeRepository.setLockscreenShadeExpansion(0f)
+            shadeTestUtil.setQsExpansion(0f)
+            shadeTestUtil.setLockscreenShadeExpansion(0f)
         }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
index 1e5f314..7a37a9e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
@@ -150,7 +150,7 @@
     @Test
     fun addMediaControlPlayingThenRemote() =
         testScope.runTest {
-            val sortedMedia by collectLastValue(underTest.sortedMedia)
+            val currentMedia by collectLastValue(underTest.currentMedia)
             val playingInstanceId = InstanceId.fakeInstanceId(123)
             val remoteInstanceId = InstanceId.fakeInstanceId(321)
             val playingData = createMediaData("app1", true, LOCAL, false, playingInstanceId)
@@ -161,8 +161,8 @@
             underTest.addSelectedUserMediaEntry(remoteData)
             underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(remoteInstanceId))
 
-            assertThat(sortedMedia?.size).isEqualTo(2)
-            assertThat(sortedMedia?.values)
+            assertThat(currentMedia?.size).isEqualTo(2)
+            assertThat(currentMedia)
                 .containsExactly(
                     MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId)),
                     MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(remoteInstanceId))
@@ -173,7 +173,7 @@
     @Test
     fun switchMediaControlsPlaying() =
         testScope.runTest {
-            val sortedMedia by collectLastValue(underTest.sortedMedia)
+            val currentMedia by collectLastValue(underTest.currentMedia)
             val playingInstanceId1 = InstanceId.fakeInstanceId(123)
             val playingInstanceId2 = InstanceId.fakeInstanceId(321)
             var playingData1 = createMediaData("app1", true, LOCAL, false, playingInstanceId1)
@@ -184,8 +184,8 @@
             underTest.addSelectedUserMediaEntry(playingData2)
             underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId2))
 
-            assertThat(sortedMedia?.size).isEqualTo(2)
-            assertThat(sortedMedia?.values)
+            assertThat(currentMedia?.size).isEqualTo(2)
+            assertThat(currentMedia)
                 .containsExactly(
                     MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId1)),
                     MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId2))
@@ -198,12 +198,28 @@
             underTest.addSelectedUserMediaEntry(playingData1)
             underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId1))
             underTest.addSelectedUserMediaEntry(playingData2)
-            underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId2))
+            underTest.addMediaDataLoadingState(
+                MediaDataLoadingModel.Loaded(playingInstanceId2, false)
+            )
 
-            assertThat(sortedMedia?.size).isEqualTo(2)
-            assertThat(sortedMedia?.values)
+            assertThat(currentMedia?.size).isEqualTo(2)
+            assertThat(currentMedia)
                 .containsExactly(
-                    MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId2)),
+                    MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId1)),
+                    MediaCommonModel.MediaControl(
+                        MediaDataLoadingModel.Loaded(playingInstanceId2, false)
+                    )
+                )
+                .inOrder()
+
+            underTest.setOrderedMedia()
+
+            assertThat(currentMedia?.size).isEqualTo(2)
+            assertThat(currentMedia)
+                .containsExactly(
+                    MediaCommonModel.MediaControl(
+                        MediaDataLoadingModel.Loaded(playingInstanceId2, false)
+                    ),
                     MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId1))
                 )
                 .inOrder()
@@ -212,7 +228,7 @@
     @Test
     fun fullOrderTest() =
         testScope.runTest {
-            val sortedMedia by collectLastValue(underTest.sortedMedia)
+            val currentMedia by collectLastValue(underTest.currentMedia)
             val instanceId1 = InstanceId.fakeInstanceId(123)
             val instanceId2 = InstanceId.fakeInstanceId(456)
             val instanceId3 = InstanceId.fakeInstanceId(321)
@@ -252,8 +268,8 @@
                 SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
             )
 
-            assertThat(sortedMedia?.size).isEqualTo(6)
-            assertThat(sortedMedia?.values)
+            assertThat(currentMedia?.size).isEqualTo(6)
+            assertThat(currentMedia)
                 .containsExactly(
                     MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(instanceId1)),
                     MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(instanceId2)),
@@ -270,7 +286,7 @@
     @Test
     fun loadMediaFromRec() =
         testScope.runTest {
-            val isMediaFromRec by collectLastValue(underTest.isMediaFromRec)
+            val currentMedia by collectLastValue(underTest.currentMedia)
             val instanceId1 = InstanceId.fakeInstanceId(123)
             val instanceId2 = InstanceId.fakeInstanceId(456)
             val data =
@@ -278,22 +294,59 @@
                     active = true,
                     instanceId = instanceId1,
                     packageName = PACKAGE_NAME,
-                    isPlaying = true
+                    isPlaying = true,
+                    notificationKey = KEY,
                 )
-            val newData = MediaData(active = true, instanceId = instanceId2)
-
-            assertThat(isMediaFromRec).isFalse()
+            val newData =
+                MediaData(
+                    active = true,
+                    instanceId = instanceId2,
+                    isPlaying = true,
+                    notificationKey = KEY_2
+                )
+            val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
+            val mediaRecommendations =
+                SmartspaceMediaData(
+                    targetId = KEY_MEDIA_SMARTSPACE,
+                    isActive = true,
+                    packageName = PACKAGE_NAME,
+                    recommendations = MediaTestHelper.getValidRecommendationList(icon),
+                )
 
             underTest.setMediaFromRecPackageName(PACKAGE_NAME)
             underTest.addSelectedUserMediaEntry(data)
+            underTest.setRecommendation(mediaRecommendations)
+            underTest.setRecommendationsLoadingState(
+                SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE)
+            )
             underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId1))
 
-            assertThat(isMediaFromRec).isTrue()
+            assertThat(currentMedia)
+                .containsExactly(
+                    MediaCommonModel.MediaControl(
+                        MediaDataLoadingModel.Loaded(instanceId1),
+                        isMediaFromRec = true
+                    ),
+                    MediaCommonModel.MediaRecommendations(
+                        SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE)
+                    )
+                )
+                .inOrder()
 
             underTest.addSelectedUserMediaEntry(newData)
+            underTest.addSelectedUserMediaEntry(data.copy(isPlaying = false))
             underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId2))
+            underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId1))
 
-            assertThat(isMediaFromRec).isFalse()
+            assertThat(currentMedia)
+                .containsExactly(
+                    MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(instanceId2)),
+                    MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(instanceId1)),
+                    MediaCommonModel.MediaRecommendations(
+                        SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE)
+                    )
+                )
+                .inOrder()
         }
 
     private fun createMediaData(
@@ -316,6 +369,7 @@
         private const val LOCAL = MediaData.PLAYBACK_LOCAL
         private const val REMOTE = MediaData.PLAYBACK_CAST_LOCAL
         private const val KEY = "KEY"
+        private const val KEY_2 = "KEY_2"
         private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
         private const val PACKAGE_NAME = "com.android.example"
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
index e44affc7..39dbc7e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
@@ -55,6 +55,14 @@
     private val mediaFilterRepository: MediaFilterRepository = kosmos.mediaFilterRepository
     private val mediaRecommendationsInteractor: MediaRecommendationsInteractor =
         kosmos.mediaRecommendationsInteractor
+    val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
+    private val mediaRecommendation =
+        SmartspaceMediaData(
+            targetId = KEY_MEDIA_SMARTSPACE,
+            isActive = true,
+            packageName = PACKAGE_NAME,
+            recommendations = MediaTestHelper.getValidRecommendationList(icon),
+        )
 
     private val underTest: MediaCarouselInteractor = kosmos.mediaCarouselInteractor
 
@@ -122,26 +130,19 @@
                 collectLastValue(underTest.hasActiveMediaOrRecommendation)
             val hasAnyMediaOrRecommendation by
                 collectLastValue(underTest.hasAnyMediaOrRecommendation)
-            val sortedMedia by collectLastValue(underTest.sortedMedia)
+            val currentMedia by collectLastValue(underTest.currentMedia)
             kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
 
-            val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
-            val userMediaRecommendation =
-                SmartspaceMediaData(
-                    targetId = KEY_MEDIA_SMARTSPACE,
-                    isActive = true,
-                    recommendations = MediaTestHelper.getValidRecommendationList(icon),
-                )
             val userMedia = MediaData(active = false)
             val recsLoadingModel = SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
             val mediaLoadingModel = MediaDataLoadingModel.Loaded(userMedia.instanceId)
 
-            mediaFilterRepository.setRecommendation(userMediaRecommendation)
+            mediaFilterRepository.setRecommendation(mediaRecommendation)
             mediaFilterRepository.setRecommendationsLoadingState(recsLoadingModel)
 
             assertThat(hasActiveMediaOrRecommendation).isTrue()
             assertThat(hasAnyMediaOrRecommendation).isTrue()
-            assertThat(sortedMedia)
+            assertThat(currentMedia)
                 .containsExactly(MediaCommonModel.MediaRecommendations(recsLoadingModel))
 
             mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
@@ -149,7 +150,7 @@
 
             assertThat(hasActiveMediaOrRecommendation).isTrue()
             assertThat(hasAnyMediaOrRecommendation).isTrue()
-            assertThat(sortedMedia)
+            assertThat(currentMedia)
                 .containsExactly(
                     MediaCommonModel.MediaRecommendations(recsLoadingModel),
                     MediaCommonModel.MediaControl(mediaLoadingModel, true)
@@ -166,14 +167,6 @@
                 collectLastValue(underTest.hasAnyMediaOrRecommendation)
             kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
 
-            val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
-            val mediaRecommendation =
-                SmartspaceMediaData(
-                    targetId = KEY_MEDIA_SMARTSPACE,
-                    isActive = true,
-                    recommendations = MediaTestHelper.getValidRecommendationList(icon),
-                )
-
             mediaFilterRepository.setRecommendation(mediaRecommendation)
 
             assertThat(hasActiveMediaOrRecommendation).isTrue()
@@ -194,14 +187,6 @@
                 collectLastValue(underTest.hasAnyMediaOrRecommendation)
             kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
 
-            val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
-            val mediaRecommendation =
-                SmartspaceMediaData(
-                    targetId = KEY_MEDIA_SMARTSPACE,
-                    isActive = true,
-                    recommendations = MediaTestHelper.getValidRecommendationList(icon),
-                )
-
             mediaFilterRepository.setRecommendation(mediaRecommendation)
 
             assertThat(hasActiveMediaOrRecommendation).isTrue()
@@ -234,26 +219,42 @@
     @Test
     fun loadMediaFromRec() =
         testScope.runTest {
-            val isMediaFromRec by collectLastValue(underTest.isMediaFromRec)
+            val currentMedia by collectLastValue(underTest.currentMedia)
             val instanceId = InstanceId.fakeInstanceId(123)
-            val data = MediaData(active = true, instanceId = instanceId, packageName = PACKAGE_NAME)
+            val data =
+                MediaData(
+                    active = true,
+                    instanceId = instanceId,
+                    packageName = PACKAGE_NAME,
+                    notificationKey = KEY
+                )
+            val smartspaceLoadingModel = SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE)
+            val mediaLoadingModel = MediaDataLoadingModel.Loaded(instanceId)
 
-            assertThat(isMediaFromRec).isFalse()
-
+            mediaFilterRepository.setRecommendation(mediaRecommendation)
+            mediaFilterRepository.setRecommendationsLoadingState(smartspaceLoadingModel)
             mediaRecommendationsInteractor.switchToMediaControl(PACKAGE_NAME)
             mediaFilterRepository.addSelectedUserMediaEntry(data)
-            mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+            mediaFilterRepository.addMediaDataLoadingState(mediaLoadingModel)
 
-            assertThat(isMediaFromRec).isFalse()
+            assertThat(currentMedia)
+                .containsExactly(MediaCommonModel.MediaRecommendations(smartspaceLoadingModel))
+                .inOrder()
 
             mediaFilterRepository.addSelectedUserMediaEntry(data.copy(isPlaying = true))
             mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
 
-            assertThat(isMediaFromRec).isTrue()
+            assertThat(currentMedia)
+                .containsExactly(
+                    MediaCommonModel.MediaControl(mediaLoadingModel, isMediaFromRec = true),
+                    MediaCommonModel.MediaRecommendations(smartspaceLoadingModel)
+                )
+                .inOrder()
         }
 
     companion object {
         private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
         private const val PACKAGE_NAME = "com.android.example"
+        private const val KEY = "key"
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
index d9224d7..365a7c3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
@@ -27,12 +27,16 @@
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.animation.Expandable
 import com.android.systemui.bluetooth.mockBroadcastDialogController
+import com.android.systemui.concurrency.fakeExecutor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.data.repository.mediaDataRepository
 import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
+import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor
 import com.android.systemui.media.controls.domain.pipeline.interactor.MediaControlInteractor
 import com.android.systemui.media.controls.domain.pipeline.interactor.mediaControlInteractor
 import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
+import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor
 import com.android.systemui.media.controls.shared.model.MediaData
 import com.android.systemui.media.controls.util.mediaInstanceId
 import com.android.systemui.media.mediaOutputDialogManager
@@ -41,16 +45,16 @@
 import com.android.systemui.statusbar.notificationLockscreenUserManager
 import com.android.systemui.statusbar.policy.keyguardStateController
 import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -117,7 +121,7 @@
         whenever(kosmos.activityIntentHelper.wouldPendingShowOverLockscreen(any(), any()))
             .thenReturn(true)
 
-        val clickIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }
+        val clickIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) }
         val expandable = mock<Expandable>()
 
         underTest.startClickIntent(expandable, clickIntent)
@@ -129,7 +133,7 @@
     fun startClickIntent_hideOverLockscreen() {
         whenever(keyguardStateController.isShowing).thenReturn(false)
 
-        val clickIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }
+        val clickIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) }
         val expandable = mock<Expandable>()
         val activityController = mock<ActivityTransitionAnimator.Controller>()
         whenever(expandable.activityTransitionController(any())).thenReturn(activityController)
@@ -146,7 +150,7 @@
         whenever(kosmos.activityIntentHelper.wouldPendingShowOverLockscreen(any(), any()))
             .thenReturn(true)
 
-        val deviceIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }
+        val deviceIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) }
 
         underTest.startDeviceIntent(deviceIntent)
 
@@ -159,7 +163,7 @@
         whenever(kosmos.activityIntentHelper.wouldPendingShowOverLockscreen(any(), any()))
             .thenReturn(true)
 
-        val deviceIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(false) }
+        val deviceIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(false) }
 
         underTest.startDeviceIntent(deviceIntent)
 
@@ -170,7 +174,7 @@
     fun startDeviceIntent_hideOverLockscreen() {
         whenever(keyguardStateController.isShowing).thenReturn(false)
 
-        val deviceIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }
+        val deviceIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) }
 
         underTest.startDeviceIntent(deviceIntent)
 
@@ -211,6 +215,21 @@
             )
     }
 
+    @Test
+    fun removeMediaControl() {
+        val listener = mock<MediaDataProcessor.Listener>()
+        kosmos.mediaDataProcessor.addInternalListener(listener)
+
+        var mediaData = MediaData(userId = USER_ID, instanceId = instanceId, artist = ARTIST)
+        kosmos.mediaDataRepository.addMediaEntry(KEY, mediaData)
+
+        underTest.removeMediaControl(null, instanceId, 0L)
+        kosmos.fakeExecutor.advanceClockToNext()
+        kosmos.fakeExecutor.runAllReady()
+
+        verify(listener).onMediaDataRemoved(eq(KEY), eq(true))
+    }
+
     companion object {
         private const val USER_ID = 0
         private const val KEY = "key"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
index d1e475f..0551bfb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
@@ -92,13 +92,29 @@
             val instanceId1 = InstanceId.fakeInstanceId(123)
             val instanceId2 = InstanceId.fakeInstanceId(456)
 
-            loadMediaControl(KEY, instanceId1)
-            loadMediaControl(KEY_2, instanceId2)
+            loadMediaControl(KEY, instanceId1, isPlaying = true)
+            loadMediaControl(KEY_2, instanceId2, isPlaying = true)
+            loadMediaControl(KEY, instanceId1, isPlaying = false)
 
-            val firstMediaControl = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl
-            val secondMediaControl = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl
-            assertThat(firstMediaControl.instanceId).isEqualTo(instanceId2)
-            assertThat(secondMediaControl.instanceId).isEqualTo(instanceId1)
+            var mediaControl2 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl
+            var mediaControl1 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl
+            assertThat(mediaControl2.instanceId).isEqualTo(instanceId2)
+            assertThat(mediaControl1.instanceId).isEqualTo(instanceId1)
+
+            loadMediaControl(KEY, instanceId1, isPlaying = true)
+            loadMediaControl(KEY_2, instanceId2, isPlaying = false)
+
+            mediaControl2 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl
+            mediaControl1 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl
+            assertThat(mediaControl2.instanceId).isEqualTo(instanceId2)
+            assertThat(mediaControl1.instanceId).isEqualTo(instanceId1)
+
+            underTest.onReorderingAllowed()
+
+            mediaControl1 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl
+            mediaControl2 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl
+            assertThat(mediaControl1.instanceId).isEqualTo(instanceId1)
+            assertThat(mediaControl2.instanceId).isEqualTo(instanceId2)
         }
 
     @Test
@@ -147,6 +163,7 @@
             val mediaControl = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl
             assertThat(sortedMedia).hasSize(2)
             assertThat(mediaControl.instanceId).isEqualTo(instanceId)
+            assertThat(mediaControl.isMediaFromRec).isTrue()
         }
 
     private fun loadMediaControl(key: String, instanceId: InstanceId, isPlaying: Boolean = true) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
index 5661bd3..9d8ec95 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
@@ -51,8 +51,8 @@
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val sceneInteractor = kosmos.sceneInteractor
-    private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
 
     private val underTest = kosmos.notificationsShadeSceneViewModel
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CloseShadeRightAfterClickTestB339290820.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CloseShadeRightAfterClickTestB339290820.kt
new file mode 100644
index 0000000..8d1aa73
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CloseShadeRightAfterClickTestB339290820.kt
@@ -0,0 +1,215 @@
+/*
+ * 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.qs.external
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.Intent
+import android.content.ServiceConnection
+import android.content.applicationContext
+import android.content.packageManager
+import android.os.Binder
+import android.os.Handler
+import android.os.RemoteException
+import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
+import android.service.quicksettings.Tile
+import android.testing.TestableContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
+import com.android.systemui.testKosmos
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.fakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class CloseShadeRightAfterClickTestB339290820 : SysuiTestCase() {
+
+    private val testableContext: TestableContext
+    private val bindDelayExecutor: FakeExecutor
+    private val kosmos =
+        testKosmos().apply {
+            testableContext = testCase.context
+            bindDelayExecutor = FakeExecutor(fakeSystemClock)
+            testableContext.setMockPackageManager(packageManager)
+            customTileSpec = TileSpec.create(testComponentName)
+            applicationContext = ContextWrapperDelayedBind(testableContext, bindDelayExecutor)
+        }
+
+    @Before
+    fun setUp() {
+        kosmos.apply {
+            whenever(packageManager.getPackageUidAsUser(anyString(), anyInt(), anyInt()))
+                .thenReturn(Binder.getCallingUid())
+            packageManagerAdapterFacade.setIsActive(true)
+            testableContext.addMockService(testComponentName, iQSTileService.asBinder())
+        }
+    }
+
+    @Test
+    @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+    fun testStopListeningShortlyAfterClick_clickIsSent() {
+        with(kosmos) {
+            val tile = FakeCustomTileInterface(tileServices)
+            // Flush any bind from startup
+            FakeExecutor.exhaustExecutors(fakeExecutor, bindDelayExecutor)
+
+            // Open QS
+            tile.setListening(true)
+            fakeExecutor.runAllReady()
+            tile.click()
+            fakeExecutor.runAllReady()
+
+            // No clicks yet because the latch is preventing the bind
+            assertThat(iQSTileService.clicks).isEmpty()
+
+            // Close QS
+            tile.setListening(false)
+            fakeExecutor.runAllReady()
+            // And finally bind
+            FakeExecutor.exhaustExecutors(fakeExecutor, bindDelayExecutor)
+
+            assertThat(iQSTileService.clicks).containsExactly(tile.token)
+        }
+    }
+}
+
+private val testComponentName = ComponentName("pkg", "srv")
+
+// This is a fake `CustomTile` that implements what we need for the test. Mainly setListening and
+// click
+private class FakeCustomTileInterface(tileServices: TileServices) : CustomTileInterface {
+    override val user: Int
+        get() = 0
+    override val qsTile: Tile = Tile()
+    override val component: ComponentName = testComponentName
+    private var listening = false
+    private val serviceManager = tileServices.getTileWrapper(this)
+    private val serviceInterface = serviceManager.tileService
+
+    val token = Binder()
+
+    override fun getTileSpec(): String {
+        return CustomTile.toSpec(component)
+    }
+
+    override fun refreshState() {}
+
+    override fun updateTileState(tile: Tile, uid: Int) {}
+
+    override fun onDialogShown() {}
+
+    override fun onDialogHidden() {}
+
+    override fun startActivityAndCollapse(pendingIntent: PendingIntent) {}
+
+    override fun startUnlockAndRun() {}
+
+    fun setListening(listening: Boolean) {
+        if (listening == this.listening) return
+        this.listening = listening
+
+        try {
+            if (listening) {
+                if (!serviceManager.isActiveTile) {
+                    serviceManager.setBindRequested(true)
+                    serviceInterface.onStartListening()
+                }
+            } else {
+                serviceInterface.onStopListening()
+                serviceManager.setBindRequested(false)
+            }
+        } catch (e: RemoteException) {
+            // Called through wrapper, won't happen here.
+        }
+    }
+
+    fun click() {
+        try {
+            if (serviceManager.isActiveTile) {
+                serviceManager.setBindRequested(true)
+                serviceInterface.onStartListening()
+            }
+            serviceInterface.onClick(token)
+        } catch (e: RemoteException) {
+            // Called through wrapper, won't happen here.
+        }
+    }
+}
+
+private class ContextWrapperDelayedBind(
+    val context: Context,
+    val executor: FakeExecutor,
+) : ContextWrapper(context) {
+    override fun bindServiceAsUser(
+        service: Intent,
+        conn: ServiceConnection,
+        flags: Int,
+        user: UserHandle
+    ): Boolean {
+        executor.execute { super.bindServiceAsUser(service, conn, flags, user) }
+        return true
+    }
+
+    override fun bindServiceAsUser(
+        service: Intent,
+        conn: ServiceConnection,
+        flags: BindServiceFlags,
+        user: UserHandle
+    ): Boolean {
+        executor.execute { super.bindServiceAsUser(service, conn, flags, user) }
+        return true
+    }
+
+    override fun bindServiceAsUser(
+        service: Intent?,
+        conn: ServiceConnection?,
+        flags: Int,
+        handler: Handler?,
+        user: UserHandle?
+    ): Boolean {
+        executor.execute { super.bindServiceAsUser(service, conn, flags, handler, user) }
+        return true
+    }
+
+    override fun bindServiceAsUser(
+        service: Intent,
+        conn: ServiceConnection,
+        flags: BindServiceFlags,
+        handler: Handler,
+        user: UserHandle
+    ): Boolean {
+        executor.execute { super.bindServiceAsUser(service, conn, flags, handler, user) }
+        return true
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepositoryTest.kt
new file mode 100644
index 0000000..1e5599b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepositoryTest.kt
@@ -0,0 +1,165 @@
+/*
+ * 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.qs.panels.data.repository
+
+import android.content.ComponentName
+import android.content.packageManager
+import android.content.pm.PackageManager
+import android.content.pm.ServiceInfo
+import android.content.pm.UserInfo
+import android.graphics.drawable.TestStubDrawable
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.kosmos.mainCoroutineContext
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.panels.shared.model.EditTileData
+import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesComponentRepository
+import com.android.systemui.qs.pipeline.data.repository.fakeInstalledTilesRepository
+import com.android.systemui.qs.pipeline.data.repository.installedTilesRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class IconAndNameCustomRepositoryTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
+    private val packageManager: PackageManager = kosmos.packageManager
+    private val userTracker: FakeUserTracker =
+        kosmos.fakeUserTracker.apply {
+            whenever(userContext.packageManager).thenReturn(packageManager)
+        }
+
+    private val service1 =
+        FakeInstalledTilesComponentRepository.ServiceInfo(
+            component1,
+            tileService1,
+            drawable1,
+            appName1,
+        )
+
+    private val service2 =
+        FakeInstalledTilesComponentRepository.ServiceInfo(
+            component2,
+            tileService2,
+            drawable2,
+            appName2,
+        )
+
+    private val underTest =
+        with(kosmos) {
+            IconAndNameCustomRepository(
+                installedTilesRepository,
+                userTracker,
+                mainCoroutineContext,
+            )
+        }
+
+    @Before
+    fun setUp() {
+        kosmos.fakeInstalledTilesRepository.setInstalledServicesForUser(
+            userTracker.userId,
+            listOf(service1, service2)
+        )
+    }
+
+    @Test
+    fun loadDataForCurrentServices() =
+        with(kosmos) {
+            testScope.runTest {
+                val editTileDataList = underTest.getCustomTileData()
+                val expectedData1 =
+                    EditTileData(
+                        TileSpec.create(component1),
+                        Icon.Loaded(drawable1, ContentDescription.Loaded(tileService1)),
+                        Text.Loaded(tileService1),
+                        Text.Loaded(appName1),
+                    )
+                val expectedData2 =
+                    EditTileData(
+                        TileSpec.create(component2),
+                        Icon.Loaded(drawable2, ContentDescription.Loaded(tileService2)),
+                        Text.Loaded(tileService2),
+                        Text.Loaded(appName2),
+                    )
+
+                assertThat(editTileDataList).containsExactly(expectedData1, expectedData2)
+            }
+        }
+
+    @Test
+    fun loadDataForCurrentServices_otherCurrentUser_empty() =
+        with(kosmos) {
+            testScope.runTest {
+                userTracker.set(listOf(UserInfo(11, "", 0)), 0)
+                val editTileDataList = underTest.getCustomTileData()
+
+                assertThat(editTileDataList).isEmpty()
+            }
+        }
+
+    @Test
+    fun loadDataForCurrentServices_serviceInfoWithNullIcon_notInList() =
+        with(kosmos) {
+            testScope.runTest {
+                val serviceNullIcon =
+                    FakeInstalledTilesComponentRepository.ServiceInfo(
+                        component2,
+                        tileService2,
+                    )
+                fakeInstalledTilesRepository.setInstalledServicesForUser(
+                    userTracker.userId,
+                    listOf(service1, serviceNullIcon)
+                )
+
+                val expectedData1 =
+                    EditTileData(
+                        TileSpec.create(component1),
+                        Icon.Loaded(drawable1, ContentDescription.Loaded(tileService1)),
+                        Text.Loaded(tileService1),
+                        Text.Loaded(appName1),
+                    )
+
+                val editTileDataList = underTest.getCustomTileData()
+                assertThat(editTileDataList).containsExactly(expectedData1)
+            }
+        }
+
+    private companion object {
+        val drawable1 = TestStubDrawable("drawable1")
+        val appName1 = "App1"
+        val tileService1 = "Tile Service 1"
+        val component1 = ComponentName("pkg1", "srv1")
+
+        val drawable2 = TestStubDrawable("drawable2")
+        val appName2 = "App2"
+        val tileService2 = "Tile Service 2"
+        val component2 = ComponentName("pkg2", "srv2")
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/StockTilesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/StockTilesRepositoryTest.kt
new file mode 100644
index 0000000..56cead1
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/StockTilesRepositoryTest.kt
@@ -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.
+ */
+
+package com.android.systemui.qs.panels.data.repository
+
+import android.content.res.mainResources
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class StockTilesRepositoryTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
+    private val underTest = StockTilesRepository(kosmos.mainResources)
+
+    @Test
+    fun stockTilesMatchesResources() {
+        val expected =
+            kosmos.mainResources
+                .getString(R.string.quick_settings_tiles_stock)
+                .split(",")
+                .map(TileSpec::create)
+        assertThat(underTest.stockTiles).isEqualTo(expected)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractorTest.kt
new file mode 100644
index 0000000..deefbf5
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractorTest.kt
@@ -0,0 +1,198 @@
+/*
+ * 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.qs.panels.domain.interactor
+
+import android.content.ComponentName
+import android.graphics.drawable.TestStubDrawable
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.panels.data.repository.iconAndNameCustomRepository
+import com.android.systemui.qs.panels.data.repository.stockTilesRepository
+import com.android.systemui.qs.panels.shared.model.EditTileData
+import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesComponentRepository
+import com.android.systemui.qs.pipeline.data.repository.fakeInstalledTilesRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.impl.battery.qsBatterySaverTileConfig
+import com.android.systemui.qs.tiles.impl.flashlight.qsFlashlightTileConfig
+import com.android.systemui.qs.tiles.impl.internet.qsInternetTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.fakeQSTileConfigProvider
+import com.android.systemui.qs.tiles.viewmodel.qSTileConfigProvider
+import com.android.systemui.settings.userTracker
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class EditTilesListInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
+    // Only have some configurations so we can test the effect of missing configurations.
+    // As the configurations are injected by dagger, we'll have all the existing configurations
+    private val internetTileConfig = kosmos.qsInternetTileConfig
+    private val flashlightTileConfig = kosmos.qsFlashlightTileConfig
+    private val batteryTileConfig = kosmos.qsBatterySaverTileConfig
+
+    private val serviceInfo =
+        FakeInstalledTilesComponentRepository.ServiceInfo(
+            component,
+            tileName,
+            icon,
+            appName,
+        )
+
+    private val underTest =
+        with(kosmos) {
+            EditTilesListInteractor(
+                stockTilesRepository,
+                qSTileConfigProvider,
+                iconAndNameCustomRepository,
+            )
+        }
+
+    @Before
+    fun setUp() {
+        with(kosmos) {
+            fakeInstalledTilesRepository.setInstalledServicesForUser(
+                userTracker.userId,
+                listOf(serviceInfo)
+            )
+
+            with(fakeQSTileConfigProvider) {
+                putConfig(internetTileConfig.tileSpec, internetTileConfig)
+                putConfig(flashlightTileConfig.tileSpec, flashlightTileConfig)
+                putConfig(batteryTileConfig.tileSpec, batteryTileConfig)
+            }
+        }
+    }
+
+    @Test
+    fun getTilesToEdit_stockTilesHaveNoAppName() =
+        with(kosmos) {
+            testScope.runTest {
+                val editTiles = underTest.getTilesToEdit()
+
+                assertThat(editTiles.stockTiles.all { it.appName == null }).isTrue()
+            }
+        }
+
+    @Test
+    fun getTilesToEdit_stockTilesAreAllPlatformSpecs() =
+        with(kosmos) {
+            testScope.runTest {
+                val editTiles = underTest.getTilesToEdit()
+
+                assertThat(editTiles.stockTiles.all { it.tileSpec is TileSpec.PlatformTileSpec })
+                    .isTrue()
+            }
+        }
+
+    @Test
+    fun getTilesToEdit_stockTiles_sameOrderAsRepository() =
+        with(kosmos) {
+            testScope.runTest {
+                val editTiles = underTest.getTilesToEdit()
+
+                assertThat(editTiles.stockTiles.map { it.tileSpec })
+                    .isEqualTo(stockTilesRepository.stockTiles)
+            }
+        }
+
+    @Test
+    fun getTilesToEdit_customTileData_matchesService() =
+        with(kosmos) {
+            testScope.runTest {
+                val editTiles = underTest.getTilesToEdit()
+                val expected =
+                    EditTileData(
+                        tileSpec = TileSpec.create(component),
+                        icon = Icon.Loaded(icon, ContentDescription.Loaded(tileName)),
+                        label = Text.Loaded(tileName),
+                        appName = Text.Loaded(appName),
+                    )
+
+                assertThat(editTiles.customTiles).hasSize(1)
+                assertThat(editTiles.customTiles[0]).isEqualTo(expected)
+            }
+        }
+
+    @Test
+    fun getTilesToEdit_tilesInConfigProvider_correctData() =
+        with(kosmos) {
+            testScope.runTest {
+                val editTiles = underTest.getTilesToEdit()
+
+                assertThat(
+                        editTiles.stockTiles.first { it.tileSpec == internetTileConfig.tileSpec }
+                    )
+                    .isEqualTo(internetTileConfig.toEditTileData())
+                assertThat(
+                        editTiles.stockTiles.first { it.tileSpec == flashlightTileConfig.tileSpec }
+                    )
+                    .isEqualTo(flashlightTileConfig.toEditTileData())
+                assertThat(editTiles.stockTiles.first { it.tileSpec == batteryTileConfig.tileSpec })
+                    .isEqualTo(batteryTileConfig.toEditTileData())
+            }
+        }
+
+    @Test
+    fun getTilesToEdit_tilesNotInConfigProvider_useDefaultData() =
+        with(kosmos) {
+            testScope.runTest {
+                underTest
+                    .getTilesToEdit()
+                    .stockTiles
+                    .filterNot { qSTileConfigProvider.hasConfig(it.tileSpec.spec) }
+                    .forEach { assertThat(it).isEqualTo(it.tileSpec.missingConfigEditTileData()) }
+            }
+        }
+
+    private companion object {
+        val component = ComponentName("pkg", "srv")
+        const val tileName = "Tile Service"
+        const val appName = "App"
+        val icon = TestStubDrawable("icon")
+
+        fun TileSpec.missingConfigEditTileData(): EditTileData {
+            return EditTileData(
+                tileSpec = this,
+                icon = Icon.Resource(android.R.drawable.star_on, ContentDescription.Loaded(spec)),
+                label = Text.Loaded(spec),
+                appName = null
+            )
+        }
+
+        fun QSTileConfig.toEditTileData(): EditTileData {
+            return EditTileData(
+                tileSpec = tileSpec,
+                icon =
+                    Icon.Resource(uiConfig.iconRes, ContentDescription.Resource(uiConfig.labelRes)),
+                label = Text.Resource(uiConfig.labelRes),
+                appName = null,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt
new file mode 100644
index 0000000..9fb25a28
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt
@@ -0,0 +1,507 @@
+/*
+ * 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.qs.panels.ui.viewmodel
+
+import android.R
+import android.content.ComponentName
+import android.graphics.drawable.TestStubDrawable
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.FakeQSFactory
+import com.android.systemui.qs.FakeQSTile
+import com.android.systemui.qs.panels.data.repository.stockTilesRepository
+import com.android.systemui.qs.panels.domain.interactor.editTilesListInteractor
+import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap
+import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor
+import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout
+import com.android.systemui.qs.panels.shared.model.EditTileData
+import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesComponentRepository
+import com.android.systemui.qs.pipeline.data.repository.MinimumTilesFixedRepository
+import com.android.systemui.qs.pipeline.data.repository.fakeInstalledTilesRepository
+import com.android.systemui.qs.pipeline.data.repository.fakeMinimumTilesRepository
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+import com.android.systemui.qs.pipeline.domain.interactor.minimumTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.qsTileFactory
+import com.android.systemui.qs.tiles.impl.alarm.qsAlarmTileConfig
+import com.android.systemui.qs.tiles.impl.battery.qsBatterySaverTileConfig
+import com.android.systemui.qs.tiles.impl.flashlight.qsFlashlightTileConfig
+import com.android.systemui.qs.tiles.impl.internet.qsInternetTileConfig
+import com.android.systemui.qs.tiles.impl.sensorprivacy.qsCameraSensorPrivacyToggleTileConfig
+import com.android.systemui.qs.tiles.impl.sensorprivacy.qsMicrophoneSensorPrivacyToggleTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.fakeQSTileConfigProvider
+import com.android.systemui.qs.tiles.viewmodel.qSTileConfigProvider
+import com.android.systemui.settings.userTracker
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class EditModeViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
+    // Only have some configurations so we can test the effect of missing configurations.
+    // As the configurations are injected by dagger, we'll have all the existing configurations
+    private val configs =
+        with(kosmos) {
+            setOf(
+                qsInternetTileConfig,
+                qsFlashlightTileConfig,
+                qsBatterySaverTileConfig,
+                qsAlarmTileConfig,
+                qsCameraSensorPrivacyToggleTileConfig,
+                qsMicrophoneSensorPrivacyToggleTileConfig,
+            )
+        }
+
+    private val serviceInfo1 =
+        FakeInstalledTilesComponentRepository.ServiceInfo(
+            component1,
+            tileService1,
+            drawable1,
+            appName1,
+        )
+
+    private val serviceInfo2 =
+        FakeInstalledTilesComponentRepository.ServiceInfo(
+            component2,
+            tileService2,
+            drawable2,
+            appName2,
+        )
+
+    private val underTest: EditModeViewModel by lazy {
+        with(kosmos) {
+            EditModeViewModel(
+                editTilesListInteractor,
+                currentTilesInteractor,
+                minimumTilesInteractor,
+                infiniteGridLayout,
+                applicationCoroutineScope,
+                gridLayoutTypeInteractor,
+                gridLayoutMap,
+            )
+        }
+    }
+
+    @Before
+    fun setUp() {
+        with(kosmos) {
+            fakeMinimumTilesRepository = MinimumTilesFixedRepository(minNumberOfTiles)
+
+            fakeInstalledTilesRepository.setInstalledServicesForUser(
+                userTracker.userId,
+                listOf(serviceInfo1, serviceInfo2)
+            )
+
+            with(fakeQSTileConfigProvider) { configs.forEach { putConfig(it.tileSpec, it) } }
+            qsTileFactory = FakeQSFactory { FakeQSTile(userTracker.userId, available = true) }
+        }
+    }
+
+    @Test
+    fun isEditing() =
+        with(kosmos) {
+            testScope.runTest {
+                val isEditing by collectLastValue(underTest.isEditing)
+
+                assertThat(isEditing).isFalse()
+
+                underTest.startEditing()
+                assertThat(isEditing).isTrue()
+
+                underTest.stopEditing()
+                assertThat(isEditing).isFalse()
+            }
+        }
+
+    @Test
+    fun editing_false_emptyFlowOfTiles() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(underTest.tiles)
+
+                assertThat(tiles).isNull()
+            }
+        }
+
+    @Test
+    fun editing_true_notEmptyTileData() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(underTest.tiles)
+
+                underTest.startEditing()
+
+                assertThat(tiles).isNotEmpty()
+            }
+        }
+
+    @Test
+    fun tilesData_hasAllStockTiles() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(underTest.tiles)
+
+                underTest.startEditing()
+
+                assertThat(
+                        tiles!!
+                            .filter { it.tileSpec is TileSpec.PlatformTileSpec }
+                            .map { it.tileSpec }
+                    )
+                    .containsExactlyElementsIn(stockTilesRepository.stockTiles)
+            }
+        }
+
+    @Test
+    fun tilesData_stockTiles_haveCorrectUiValues() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(underTest.tiles)
+
+                underTest.startEditing()
+
+                tiles!!
+                    .filter { it.tileSpec is TileSpec.PlatformTileSpec }
+                    .forEach {
+                        val data = getEditTileData(it.tileSpec)
+
+                        assertThat(it.label).isEqualTo(data.label)
+                        assertThat(it.icon).isEqualTo(data.icon)
+                        assertThat(it.appName).isNull()
+                    }
+            }
+        }
+
+    @Test
+    fun tilesData_hasAllCustomTiles() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(underTest.tiles)
+
+                underTest.startEditing()
+
+                assertThat(
+                        tiles!!
+                            .filter { it.tileSpec is TileSpec.CustomTileSpec }
+                            .map { it.tileSpec }
+                    )
+                    .containsExactly(TileSpec.create(component1), TileSpec.create(component2))
+            }
+        }
+
+    @Test
+    fun tilesData_customTiles_haveCorrectUiValues() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(underTest.tiles)
+
+                underTest.startEditing()
+
+                // service1
+                val model1 = tiles!!.first { it.tileSpec == TileSpec.create(component1) }
+                assertThat(model1.label).isEqualTo(Text.Loaded(tileService1))
+                assertThat(model1.appName).isEqualTo(Text.Loaded(appName1))
+                assertThat(model1.icon)
+                    .isEqualTo(Icon.Loaded(drawable1, ContentDescription.Loaded(tileService1)))
+
+                // service2
+                val model2 = tiles!!.first { it.tileSpec == TileSpec.create(component2) }
+                assertThat(model2.label).isEqualTo(Text.Loaded(tileService2))
+                assertThat(model2.appName).isEqualTo(Text.Loaded(appName2))
+                assertThat(model2.icon)
+                    .isEqualTo(Icon.Loaded(drawable2, ContentDescription.Loaded(tileService2)))
+            }
+        }
+
+    @Test
+    fun currentTiles_inCorrectOrder_markedAsCurrent() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(underTest.tiles)
+                val currentTiles =
+                    listOf(
+                        TileSpec.create("flashlight"),
+                        TileSpec.create("airplane"),
+                        TileSpec.create(component2),
+                        TileSpec.create("alarm"),
+                    )
+                currentTilesInteractor.setTiles(currentTiles)
+
+                underTest.startEditing()
+
+                assertThat(tiles!!.filter { it.isCurrent }.map { it.tileSpec })
+                    .containsExactlyElementsIn(currentTiles)
+                    .inOrder()
+            }
+        }
+
+    @Test
+    fun notCurrentTiles() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(underTest.tiles)
+                val currentTiles =
+                    listOf(
+                        TileSpec.create("flashlight"),
+                        TileSpec.create("airplane"),
+                        TileSpec.create(component2),
+                        TileSpec.create("alarm"),
+                    )
+                val remainingTiles =
+                    stockTilesRepository.stockTiles.filterNot { it in currentTiles } +
+                        listOf(TileSpec.create(component1))
+                currentTilesInteractor.setTiles(currentTiles)
+
+                underTest.startEditing()
+
+                assertThat(tiles!!.filterNot { it.isCurrent }.map { it.tileSpec })
+                    .containsExactlyElementsIn(remainingTiles)
+            }
+        }
+
+    @Test
+    fun currentTilesChange_trackingChange() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(underTest.tiles)
+                val currentTiles =
+                    mutableListOf(
+                        TileSpec.create("flashlight"),
+                        TileSpec.create("airplane"),
+                        TileSpec.create(component2),
+                        TileSpec.create("alarm"),
+                    )
+                currentTilesInteractor.setTiles(currentTiles)
+
+                underTest.startEditing()
+
+                val newTile = TileSpec.create("internet")
+                val position = 1
+                currentTilesInteractor.addTile(newTile, position)
+                currentTiles.add(position, newTile)
+
+                assertThat(tiles!!.filter { it.isCurrent }.map { it.tileSpec })
+                    .containsExactlyElementsIn(currentTiles)
+                    .inOrder()
+            }
+        }
+
+    @Test
+    fun nonCurrentTiles_orderPreservedWhenCurrentTilesChange() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(underTest.tiles)
+                val currentTiles =
+                    mutableListOf(
+                        TileSpec.create("flashlight"),
+                        TileSpec.create("airplane"),
+                        TileSpec.create(component2),
+                        TileSpec.create("alarm"),
+                    )
+                currentTilesInteractor.setTiles(currentTiles)
+
+                underTest.startEditing()
+
+                val nonCurrentSpecs = tiles!!.filterNot { it.isCurrent }.map { it.tileSpec }
+                val newTile = TileSpec.create("internet")
+                currentTilesInteractor.addTile(newTile)
+
+                assertThat(tiles!!.filterNot { it.isCurrent }.map { it.tileSpec })
+                    .containsExactlyElementsIn(nonCurrentSpecs - listOf(newTile))
+                    .inOrder()
+            }
+        }
+
+    @Test
+    fun nonCurrentTiles_haveOnlyAddAction() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(underTest.tiles)
+                val currentTiles =
+                    mutableListOf(
+                        TileSpec.create("flashlight"),
+                        TileSpec.create("airplane"),
+                        TileSpec.create(component2),
+                        TileSpec.create("alarm"),
+                    )
+                currentTilesInteractor.setTiles(currentTiles)
+
+                underTest.startEditing()
+
+                tiles!!
+                    .filterNot { it.isCurrent }
+                    .forEach {
+                        assertThat(it.availableEditActions)
+                            .containsExactly(AvailableEditActions.ADD)
+                    }
+            }
+        }
+
+    @Test
+    fun currentTiles_moreThanMinimumTiles_haveRemoveAction() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(underTest.tiles)
+                val currentTiles =
+                    mutableListOf(
+                        TileSpec.create("flashlight"),
+                        TileSpec.create("airplane"),
+                        TileSpec.create(component2),
+                        TileSpec.create("alarm"),
+                    )
+                currentTilesInteractor.setTiles(currentTiles)
+                assertThat(currentTiles.size).isGreaterThan(minNumberOfTiles)
+
+                underTest.startEditing()
+
+                tiles!!
+                    .filter { it.isCurrent }
+                    .forEach {
+                        assertThat(it.availableEditActions).contains(AvailableEditActions.REMOVE)
+                    }
+            }
+        }
+
+    @Test
+    fun currentTiles_minimumTiles_dontHaveRemoveAction() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(underTest.tiles)
+                val currentTiles =
+                    mutableListOf(
+                        TileSpec.create("flashlight"),
+                        TileSpec.create("airplane"),
+                        TileSpec.create(component2),
+                    )
+                currentTilesInteractor.setTiles(currentTiles)
+                assertThat(currentTiles.size).isEqualTo(minNumberOfTiles)
+
+                underTest.startEditing()
+
+                tiles!!
+                    .filter { it.isCurrent }
+                    .forEach {
+                        assertThat(it.availableEditActions)
+                            .doesNotContain(AvailableEditActions.REMOVE)
+                    }
+            }
+        }
+
+    @Test
+    fun currentTiles_lessThanMinimumTiles_dontHaveRemoveAction() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(underTest.tiles)
+                val currentTiles =
+                    mutableListOf(
+                        TileSpec.create("flashlight"),
+                        TileSpec.create("airplane"),
+                    )
+                currentTilesInteractor.setTiles(currentTiles)
+                assertThat(currentTiles.size).isLessThan(minNumberOfTiles)
+
+                underTest.startEditing()
+
+                tiles!!
+                    .filter { it.isCurrent }
+                    .forEach {
+                        assertThat(it.availableEditActions)
+                            .doesNotContain(AvailableEditActions.REMOVE)
+                    }
+            }
+        }
+
+    @Test
+    fun currentTiles_haveMoveAction() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(underTest.tiles)
+                val currentTiles =
+                    mutableListOf(
+                        TileSpec.create("flashlight"),
+                        TileSpec.create("airplane"),
+                        TileSpec.create(component2),
+                        TileSpec.create("alarm"),
+                    )
+                currentTilesInteractor.setTiles(currentTiles)
+
+                underTest.startEditing()
+
+                tiles!!
+                    .filter { it.isCurrent }
+                    .forEach {
+                        assertThat(it.availableEditActions).contains(AvailableEditActions.MOVE)
+                    }
+            }
+        }
+
+    private companion object {
+        val drawable1 = TestStubDrawable("drawable1")
+        val appName1 = "App1"
+        val tileService1 = "Tile Service 1"
+        val component1 = ComponentName("pkg1", "srv1")
+
+        val drawable2 = TestStubDrawable("drawable2")
+        val appName2 = "App2"
+        val tileService2 = "Tile Service 2"
+        val component2 = ComponentName("pkg2", "srv2")
+
+        fun TileSpec.missingConfigEditTileData(): EditTileData {
+            return EditTileData(
+                tileSpec = this,
+                icon = Icon.Resource(R.drawable.star_on, ContentDescription.Loaded(spec)),
+                label = Text.Loaded(spec),
+                appName = null
+            )
+        }
+
+        fun QSTileConfig.toEditTileData(): EditTileData {
+            return EditTileData(
+                tileSpec = tileSpec,
+                icon =
+                    Icon.Resource(uiConfig.iconRes, ContentDescription.Resource(uiConfig.labelRes)),
+                label = Text.Resource(uiConfig.labelRes),
+                appName = null,
+            )
+        }
+
+        fun Kosmos.getEditTileData(tileSpec: TileSpec): EditTileData {
+            return if (qSTileConfigProvider.hasConfig(tileSpec.spec)) {
+                qSTileConfigProvider.getConfig(tileSpec.spec).toEditTileData()
+            } else {
+                tileSpec.missingConfigEditTileData()
+            }
+        }
+
+        val minNumberOfTiles = 3
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
index bc57ce6..a0dec8c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
@@ -90,7 +90,7 @@
     }
 
     @Test
-    fun componentsLoadedOnStart() =
+    fun servicesLoadedOnStart() =
         testScope.runTest {
             val userId = 0
             val resolveInfo =
@@ -106,12 +106,14 @@
 
             val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
             runCurrent()
+            val services = underTest.getInstalledTilesServiceInfos(userId)
 
             assertThat(componentNames).containsExactly(TEST_COMPONENT)
+            assertThat(services).containsExactly(resolveInfo.serviceInfo)
         }
 
     @Test
-    fun componentAdded_foundAfterPackageChange() =
+    fun serviceAdded_foundAfterPackageChange() =
         testScope.runTest {
             val userId = 0
             val resolveInfo =
@@ -132,12 +134,14 @@
                 .thenReturn(listOf(resolveInfo))
             kosmos.fakePackageChangeRepository.notifyChange(PackageChangeModel.Empty)
             runCurrent()
+            val services = underTest.getInstalledTilesServiceInfos(userId)
 
             assertThat(componentNames).containsExactly(TEST_COMPONENT)
+            assertThat(services).containsExactly(resolveInfo.serviceInfo)
         }
 
     @Test
-    fun componentWithoutPermission_notValid() =
+    fun serviceWithoutPermission_notValid() =
         testScope.runTest {
             val userId = 0
             val resolveInfo =
@@ -152,13 +156,15 @@
                 .thenReturn(listOf(resolveInfo))
 
             val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+            val services = underTest.getInstalledTilesServiceInfos(userId)
             runCurrent()
 
             assertThat(componentNames).isEmpty()
+            assertThat(services).isEmpty()
         }
 
     @Test
-    fun componentNotEnabled_notValid() =
+    fun serviceNotEnabled_notValid() =
         testScope.runTest {
             val userId = 0
             val resolveInfo =
@@ -173,9 +179,11 @@
                 .thenReturn(listOf(resolveInfo))
 
             val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+            val services = underTest.getInstalledTilesServiceInfos(userId)
             runCurrent()
 
             assertThat(componentNames).isEmpty()
+            assertThat(services).isEmpty()
         }
 
     @Test
@@ -221,30 +229,22 @@
 
             val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
             runCurrent()
+            val service = underTest.getInstalledTilesServiceInfos(userId)
 
             assertThat(componentNames).containsExactly(TEST_COMPONENT)
+            assertThat(service).containsExactly(resolveInfo.serviceInfo)
         }
 
     @Test
-    fun loadComponentsForSameUserTwice_returnsSameFlow() =
+    fun loadServicesForSameUserTwice_returnsSameFlow() =
         testScope.runTest {
-            val flowForUser1 = underTest.getInstalledTilesComponents(1)
-            val flowForUser1TheSecondTime = underTest.getInstalledTilesComponents(1)
+            val flowForUser1 = underTest.getInstalledTilesServiceInfos(1)
+            val flowForUser1TheSecondTime = underTest.getInstalledTilesServiceInfos(1)
             runCurrent()
 
             assertThat(flowForUser1TheSecondTime).isEqualTo(flowForUser1)
         }
 
-    @Test
-    fun loadComponentsForDifferentUsers_returnsDifferentFlow() =
-        testScope.runTest {
-            val flowForUser1 = underTest.getInstalledTilesComponents(1)
-            val flowForUser2 = underTest.getInstalledTilesComponents(2)
-            runCurrent()
-
-            assertThat(flowForUser2).isNotEqualTo(flowForUser1)
-        }
-
     // Tests that a ServiceInfo that is returned by queryIntentServicesAsUser but shortly
     // after uninstalled, doesn't crash SystemUI.
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractorTest.kt
index 61e4774..3faab50 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractorTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.accessibility.data.repository.FakeAccessibilityQsShortcutsRepository
 import com.android.systemui.qs.FakeQSFactory
+import com.android.systemui.qs.FakeQSTile
 import com.android.systemui.qs.pipeline.domain.model.TileModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.ColorCorrectionTile
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
index 8ae9172..167eff1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.qs.FakeQSTile
 import com.android.systemui.qs.pipeline.data.repository.FakeAutoAddRepository
 import com.android.systemui.qs.pipeline.domain.autoaddable.FakeAutoAddable
 import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 634c5fa..1c73fe2b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.qs.QSTile.BooleanState
 import com.android.systemui.qs.FakeQSFactory
+import com.android.systemui.qs.FakeQSTile
 import com.android.systemui.qs.external.CustomTile
 import com.android.systemui.qs.external.CustomTileStatePersister
 import com.android.systemui.qs.external.TileLifecycleManager
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
index 90c8304..260189d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.qs.FakeQSFactory
+import com.android.systemui.qs.FakeQSTile
 import com.android.systemui.qs.pipeline.data.model.RestoreData
 import com.android.systemui.qs.pipeline.data.repository.FakeDefaultTilesRepository
 import com.android.systemui.qs.pipeline.data.repository.MinimumTilesFixedRepository
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
index 4207a9c..dffd0d7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.qs.FakeQSFactory
+import com.android.systemui.qs.FakeQSTile
 import com.android.systemui.qs.pipeline.data.model.RestoreData
 import com.android.systemui.qs.pipeline.data.repository.fakeRestoreRepository
 import com.android.systemui.qs.pipeline.data.repository.fakeTileSpecRepository
@@ -54,9 +55,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 class WorkProfileAutoAddedAfterRestoreTest : SysuiTestCase() {
 
-    private val kosmos by lazy {
-        Kosmos().apply { fakeUserTracker.set(listOf(USER_0_INFO), 0) }
-    }
+    private val kosmos by lazy { Kosmos().apply { fakeUserTracker.set(listOf(USER_0_INFO), 0) } }
     // Getter here so it can change when there is a managed profile.
     private val workTileAvailable: Boolean
         get() = hasManagedProfile()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
index bf48784..02a8141 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
@@ -69,6 +69,15 @@
     }
 
     @Test
+    fun testPassesIntentToStarter_dismissShadeAndShowOverLockScreenWhenLocked() {
+        val intent = Intent("test.ACTION")
+
+        underTest.handle(null, intent, true)
+
+        verify(activityStarter).startActivity(eq(intent), eq(true), any(), eq(true))
+    }
+
+    @Test
     fun testPassesActivityPendingIntentToStarterAsPendingIntent() {
         val pendingIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
index da60c18..dfc004a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
@@ -30,10 +30,10 @@
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
 import com.android.systemui.qs.tiles.impl.custom.commons.copy
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
 import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
 import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
 import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade
-import com.android.systemui.qs.tiles.impl.custom.tileSpec
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.first
@@ -47,11 +47,11 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 class CustomTileRepositoryTest : SysuiTestCase() {
 
-    private val kosmos = Kosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
+    private val kosmos = Kosmos().apply { customTileSpec = TileSpec.create(TEST_COMPONENT) }
     private val underTest: CustomTileRepository =
         with(kosmos) {
             CustomTileRepositoryImpl(
-                tileSpec,
+                customTileSpec,
                 customTileStatePersister,
                 packageManagerAdapterFacade.packageManagerAdapter,
                 testScope.testScheduler,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt
index a5c5544..a29289a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt
@@ -38,8 +38,8 @@
 import com.android.systemui.qs.tiles.impl.custom.customTilePackagesUpdatesRepository
 import com.android.systemui.qs.tiles.impl.custom.customTileRepository
 import com.android.systemui.qs.tiles.impl.custom.customTileServiceInteractor
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
 import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
-import com.android.systemui.qs.tiles.impl.custom.tileSpec
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.user.data.repository.userRepository
@@ -60,12 +60,12 @@
     private val kosmos =
         testKosmos().apply {
             componentName = TEST_COMPONENT
-            tileSpec = TileSpec.create(componentName)
+            customTileSpec = TileSpec.create(componentName)
         }
     private val underTest =
         with(kosmos) {
             CustomTileDataInteractor(
-                tileSpec = tileSpec,
+                tileSpec = customTileSpec,
                 defaultsRepository = customTileDefaultsRepository,
                 serviceInteractor = customTileServiceInteractor,
                 customTileInteractor = customTileInteractor,
@@ -180,7 +180,7 @@
                 setup()
                 customTileDefaultsRepository.putDefaults(
                     TEST_USER_1.userHandle,
-                    tileSpec.componentName,
+                    customTileSpec.componentName,
                     CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label),
                 )
 
@@ -198,7 +198,7 @@
                 setup()
                 customTileDefaultsRepository.putDefaults(
                     TEST_USER_1.userHandle,
-                    tileSpec.componentName,
+                    customTileSpec.componentName,
                     CustomTileDefaults.Error,
                 )
 
@@ -216,7 +216,7 @@
                 setup()
                 customTileDefaultsRepository.putDefaults(
                     TEST_USER_2.userHandle,
-                    tileSpec.componentName,
+                    customTileSpec.componentName,
                     CustomTileDefaults.Error,
                 )
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
index 9546a32..33299d9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
@@ -31,9 +31,9 @@
 import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
 import com.android.systemui.qs.tiles.impl.custom.customTileDefaultsRepository
 import com.android.systemui.qs.tiles.impl.custom.customTileRepository
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
 import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
 import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
-import com.android.systemui.qs.tiles.impl.custom.tileSpec
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -50,12 +50,12 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 class CustomTileInteractorTest : SysuiTestCase() {
 
-    private val kosmos = testKosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
+    private val kosmos = testKosmos().apply { customTileSpec = TileSpec.create(TEST_COMPONENT) }
 
     private val underTest: CustomTileInteractor =
         with(kosmos) {
             CustomTileInteractor(
-                tileSpec = tileSpec,
+                tileSpec = customTileSpec,
                 defaultsRepository = customTileDefaultsRepository,
                 customTileRepository = customTileRepository,
                 tileScope = testScope.backgroundScope,
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 a2127a4..3972938 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
@@ -33,9 +33,9 @@
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject.Companion.assertThat
 import com.android.systemui.qs.tiles.impl.custom.customTileQsTileConfig
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
 import com.android.systemui.qs.tiles.impl.custom.domain.CustomTileMapper
 import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
-import com.android.systemui.qs.tiles.impl.custom.tileSpec
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
@@ -51,7 +51,8 @@
 class CustomTileMapperTest : SysuiTestCase() {
 
     private val uriGrantsManager: IUriGrantsManager = mock {}
-    private val kosmos = testKosmos().apply { tileSpec = TileSpec.Companion.create(TEST_COMPONENT) }
+    private val kosmos =
+        testKosmos().apply { customTileSpec = TileSpec.Companion.create(TEST_COMPONENT) }
     private val underTest by lazy {
         CustomTileMapper(
             context = mock { whenever(createContextAsUser(any(), any())).thenReturn(context) },
@@ -202,7 +203,7 @@
     ) =
         CustomTileDataModel(
             UserHandle.of(1),
-            tileSpec.componentName,
+            customTileSpec.componentName,
             Tile().apply {
                 state = tileState
                 label = "test label"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt
index c709f16..72e5766 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt
@@ -44,9 +44,9 @@
 import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
 import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.longClick
 import com.android.systemui.qs.tiles.impl.custom.customTileServiceInteractor
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
 import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
 import com.android.systemui.qs.tiles.impl.custom.qsTileLogger
-import com.android.systemui.qs.tiles.impl.custom.tileSpec
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.any
@@ -68,7 +68,7 @@
     private val kosmos =
         testKosmos().apply {
             componentName = TEST_COMPONENT
-            tileSpec = TileSpec.create(componentName)
+            customTileSpec = TileSpec.create(componentName)
             testCase = this@CustomTileUserActionInteractorTest
         }
 
@@ -79,7 +79,7 @@
                     mock {
                         whenever(packageManager).thenReturn(packageManagerFacade.packageManager)
                     },
-                tileSpec = tileSpec,
+                tileSpec = customTileSpec,
                 qsTileLogger = qsTileLogger,
                 windowManager = windowManagerFacade.windowManager,
                 displayTracker = mock {},
@@ -227,7 +227,7 @@
     private fun pendingIntent(): PendingIntent = mock { whenever(isActivity).thenReturn(true) }
 
     private fun Kosmos.customTileModel(
-        componentName: ComponentName = tileSpec.componentName,
+        componentName: ComponentName = customTileSpec.componentName,
         activityLaunchForClick: PendingIntent? = null,
         tileState: Int = 111,
     ) =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt
new file mode 100644
index 0000000..a0aa2d4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.qs.tiles.impl.night.domain.interactor
+
+import android.hardware.display.ColorDisplayManager
+import android.hardware.display.NightDisplayListener
+import android.os.UserHandle
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.NightDisplayRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.utils.UserScopedService
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.fakeGlobalSettings
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.util.time.DateFormatUtil
+import com.android.systemui.utils.leaks.FakeLocationController
+import com.google.common.truth.Truth.assertThat
+import java.time.LocalTime
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NightDisplayTileDataInteractorTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val testUser = UserHandle.of(1)!!
+    private val testStartTime = LocalTime.MIDNIGHT
+    private val testEndTime = LocalTime.NOON
+    private val colorDisplayManager =
+        mock<ColorDisplayManager> {
+            whenever(nightDisplayAutoMode).thenReturn(ColorDisplayManager.AUTO_MODE_DISABLED)
+            whenever(isNightDisplayActivated).thenReturn(false)
+            whenever(nightDisplayCustomStartTime).thenReturn(testStartTime)
+            whenever(nightDisplayCustomEndTime).thenReturn(testEndTime)
+        }
+    private val locationController = FakeLocationController(LeakCheck())
+    private val nightDisplayListener = mock<NightDisplayListener>()
+    private val listenerBuilder =
+        mock<NightDisplayListenerModule.Builder> {
+            whenever(setUser(anyInt())).thenReturn(this)
+            whenever(build()).thenReturn(nightDisplayListener)
+        }
+    private val globalSettings = kosmos.fakeGlobalSettings
+    private val secureSettings = kosmos.fakeSettings
+    private val dateFormatUtil = mock<DateFormatUtil> { whenever(is24HourFormat).thenReturn(false) }
+    private val testDispatcher = StandardTestDispatcher()
+    private val scope = TestScope(testDispatcher)
+    private val userScopedColorDisplayManager =
+        mock<UserScopedService<ColorDisplayManager>> {
+            whenever(forUser(eq(testUser))).thenReturn(colorDisplayManager)
+        }
+    private val nightDisplayRepository =
+        NightDisplayRepository(
+            testDispatcher,
+            scope.backgroundScope,
+            globalSettings,
+            secureSettings,
+            listenerBuilder,
+            userScopedColorDisplayManager,
+            locationController,
+        )
+
+    private val underTest: NightDisplayTileDataInteractor =
+        NightDisplayTileDataInteractor(context, dateFormatUtil, nightDisplayRepository)
+
+    @Test
+    fun availability_matchesColorDisplayManager() = runTest {
+        val availability by collectLastValue(underTest.availability(testUser))
+
+        val expectedAvailability = ColorDisplayManager.isNightDisplayAvailable(context)
+        assertThat(availability).isEqualTo(expectedAvailability)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..adc8bcb
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt
@@ -0,0 +1,177 @@
+/*
+ * 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.qs.tiles.impl.night.domain.interactor
+
+import android.hardware.display.ColorDisplayManager
+import android.hardware.display.NightDisplayListener
+import android.os.UserHandle
+import android.provider.Settings
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.NightDisplayRepository
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.intentInputs
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.custom.qsTileLogger
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.user.utils.UserScopedService
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.fakeGlobalSettings
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.utils.leaks.FakeLocationController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NightDisplayTileUserActionInteractorTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler()
+    private val testUser = UserHandle.of(1)
+    private val colorDisplayManager =
+        mock<ColorDisplayManager> {
+            whenever(nightDisplayAutoMode).thenReturn(ColorDisplayManager.AUTO_MODE_DISABLED)
+            whenever(isNightDisplayActivated).thenReturn(false)
+        }
+    private val locationController = FakeLocationController(LeakCheck())
+    private val nightDisplayListener = mock<NightDisplayListener>()
+    private val listenerBuilder =
+        mock<NightDisplayListenerModule.Builder> {
+            whenever(setUser(ArgumentMatchers.anyInt())).thenReturn(this)
+            whenever(build()).thenReturn(nightDisplayListener)
+        }
+    private val globalSettings = kosmos.fakeGlobalSettings
+    private val secureSettings = kosmos.fakeSettings
+    private val testDispatcher = StandardTestDispatcher()
+    private val scope = TestScope(testDispatcher)
+    private val userScopedColorDisplayManager =
+        mock<UserScopedService<ColorDisplayManager>> {
+            whenever(forUser(eq(testUser))).thenReturn(colorDisplayManager)
+        }
+    private val nightDisplayRepository =
+        NightDisplayRepository(
+            testDispatcher,
+            scope.backgroundScope,
+            globalSettings,
+            secureSettings,
+            listenerBuilder,
+            userScopedColorDisplayManager,
+            locationController,
+        )
+
+    private val underTest =
+        NightDisplayTileUserActionInteractor(
+            nightDisplayRepository,
+            qsTileIntentUserActionHandler,
+            kosmos.qsTileLogger
+        )
+
+    @Test
+    fun handleClick_inactive_activates() =
+        scope.runTest {
+            val startingModel = NightDisplayTileModel.AutoModeOff(false, false)
+
+            underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser))
+
+            verify(colorDisplayManager).setNightDisplayActivated(true)
+        }
+
+    @Test
+    fun handleClick_active_disables() =
+        scope.runTest {
+            val startingModel = NightDisplayTileModel.AutoModeOff(true, false)
+
+            underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser))
+
+            verify(colorDisplayManager).setNightDisplayActivated(false)
+        }
+
+    @Test
+    fun handleClick_whenAutoModeTwilight_flipsState() =
+        scope.runTest {
+            val originalState = true
+            val startingModel = NightDisplayTileModel.AutoModeTwilight(originalState, false, false)
+
+            underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser))
+
+            verify(colorDisplayManager).setNightDisplayActivated(!originalState)
+        }
+
+    @Test
+    fun handleClick_whenAutoModeCustom_flipsState() =
+        scope.runTest {
+            val originalState = true
+            val startingModel =
+                NightDisplayTileModel.AutoModeCustom(originalState, false, null, null, false)
+
+            underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser))
+
+            verify(colorDisplayManager).setNightDisplayActivated(!originalState)
+        }
+
+    @Test
+    fun handleLongClickWhenEnabled() =
+        scope.runTest {
+            val enabledState = true
+
+            underTest.handleInput(
+                QSTileInputTestKtx.longClick(
+                    NightDisplayTileModel.AutoModeOff(enabledState, false),
+                    testUser
+                )
+            )
+
+            assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+
+            val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+            val actualIntentAction = intentInput.intent.action
+            val expectedIntentAction = Settings.ACTION_NIGHT_DISPLAY_SETTINGS
+            assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+        }
+
+    @Test
+    fun handleLongClickWhenDisabled() =
+        scope.runTest {
+            val enabledState = false
+
+            underTest.handleInput(
+                QSTileInputTestKtx.longClick(
+                    NightDisplayTileModel.AutoModeOff(enabledState, false),
+                    testUser
+                )
+            )
+
+            assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+
+            val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+            val actualIntentAction = intentInput.intent.action
+            val expectedIntentAction = Settings.ACTION_NIGHT_DISPLAY_SETTINGS
+            assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+        }
+}
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
new file mode 100644
index 0000000..5d2e701
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
@@ -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.systemui.qs.tiles.impl.night.ui
+
+import android.graphics.drawable.TestStubDrawable
+import android.service.quicksettings.Tile
+import android.text.TextUtils
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.qs.tiles.impl.night.qsNightDisplayTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.mock
+import java.time.LocalTime
+import java.time.format.DateTimeFormatter
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NightDisplayTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val config = kosmos.qsNightDisplayTileConfig
+
+    private val testStartTime = LocalTime.MIDNIGHT
+    private val testEndTime = LocalTime.NOON
+
+    private lateinit var mapper: NightDisplayTileMapper
+
+    @Before
+    fun setup() {
+        mapper =
+            NightDisplayTileMapper(
+                context.orCreateTestableResources
+                    .apply {
+                        addOverride(R.drawable.qs_nightlight_icon_on, TestStubDrawable())
+                        addOverride(R.drawable.qs_nightlight_icon_off, TestStubDrawable())
+                    }
+                    .resources,
+                context.theme,
+                mock<QSTileLogger>(),
+            )
+    }
+
+    @Test
+    fun disabledModel_whenAutoModeOff() {
+        val inputModel = NightDisplayTileModel.AutoModeOff(false, false)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.INACTIVE,
+                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE]
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    /** Force enable does not change the mode by itself. */
+    @Test
+    fun disabledModel_whenAutoModeOff_whenForceEnable() {
+        val inputModel = NightDisplayTileModel.AutoModeOff(false, true)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.INACTIVE,
+                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE]
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun enabledModel_whenAutoModeOff() {
+        val inputModel = NightDisplayTileModel.AutoModeOff(true, false)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.ACTIVE,
+                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE]
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun enabledModel_forceAutoMode_whenAutoModeOff() {
+        val inputModel = NightDisplayTileModel.AutoModeOff(true, true)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.ACTIVE,
+                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE]
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun enabledModel_autoModeTwilight_locationOff() {
+        val inputModel = NightDisplayTileModel.AutoModeTwilight(true, false, false)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState = createNightDisplayTileState(QSTileState.ActivationState.ACTIVE, null)
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun enabledModel_autoModeTwilight_locationOn() {
+        val inputModel = NightDisplayTileModel.AutoModeTwilight(true, false, true)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.ACTIVE,
+                context.getString(R.string.quick_settings_night_secondary_label_until_sunrise)
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun disabledModel_autoModeTwilight_locationOn() {
+        val inputModel = NightDisplayTileModel.AutoModeTwilight(false, false, true)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.INACTIVE,
+                context.getString(R.string.quick_settings_night_secondary_label_on_at_sunset)
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun disabledModel_autoModeTwilight_locationOff() {
+        val inputModel = NightDisplayTileModel.AutoModeTwilight(false, false, false)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState = createNightDisplayTileState(QSTileState.ActivationState.INACTIVE, null)
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun disabledModel_autoModeCustom_24Hour() {
+        val inputModel =
+            NightDisplayTileModel.AutoModeCustom(false, false, testStartTime, null, true)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.INACTIVE,
+                context.getString(
+                    R.string.quick_settings_night_secondary_label_on_at,
+                    formatter24Hour.format(testStartTime)
+                )
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun disabledModel_autoModeCustom_12Hour() {
+        val inputModel =
+            NightDisplayTileModel.AutoModeCustom(false, false, testStartTime, null, false)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.INACTIVE,
+                context.getString(
+                    R.string.quick_settings_night_secondary_label_on_at,
+                    formatter12Hour.format(testStartTime)
+                )
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    /** Should have the same outcome as [disabledModel_autoModeCustom_12Hour] */
+    @Test
+    fun disabledModel_autoModeCustom_12Hour_isEnrolledForcedAutoMode() {
+        val inputModel =
+            NightDisplayTileModel.AutoModeCustom(false, true, testStartTime, null, false)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.INACTIVE,
+                context.getString(
+                    R.string.quick_settings_night_secondary_label_on_at,
+                    formatter12Hour.format(testStartTime)
+                )
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun enabledModel_autoModeCustom_24Hour() {
+        val inputModel = NightDisplayTileModel.AutoModeCustom(true, false, null, testEndTime, true)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.ACTIVE,
+                context.getString(
+                    R.string.quick_settings_secondary_label_until,
+                    formatter24Hour.format(testEndTime)
+                )
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun enabledModel_autoModeCustom_12Hour() {
+        val inputModel = NightDisplayTileModel.AutoModeCustom(true, false, null, testEndTime, false)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.ACTIVE,
+                context.getString(
+                    R.string.quick_settings_secondary_label_until,
+                    formatter12Hour.format(testEndTime)
+                )
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    /** Should have the same state as [enabledModel_autoModeCustom_24Hour] */
+    @Test
+    fun enabledModel_autoModeCustom_24Hour_forceEnabled() {
+        val inputModel = NightDisplayTileModel.AutoModeCustom(true, true, null, testEndTime, true)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createNightDisplayTileState(
+                QSTileState.ActivationState.ACTIVE,
+                context.getString(
+                    R.string.quick_settings_secondary_label_until,
+                    formatter24Hour.format(testEndTime)
+                )
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    private fun createNightDisplayTileState(
+        activationState: QSTileState.ActivationState,
+        secondaryLabel: String?
+    ): QSTileState {
+        val label = context.getString(R.string.quick_settings_night_display_label)
+
+        val contentDescription =
+            if (TextUtils.isEmpty(secondaryLabel)) label
+            else TextUtils.concat(label, ", ", secondaryLabel)
+        return QSTileState(
+            {
+                Icon.Loaded(
+                    context.getDrawable(
+                        if (activationState == QSTileState.ActivationState.ACTIVE)
+                            R.drawable.qs_nightlight_icon_on
+                        else R.drawable.qs_nightlight_icon_off
+                    )!!,
+                    null
+                )
+            },
+            label,
+            activationState,
+            secondaryLabel,
+            setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+            contentDescription,
+            null,
+            QSTileState.SideViewIcon.None,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+    }
+
+    private companion object {
+        val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a")
+        val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileDataInteractorTest.kt
new file mode 100644
index 0000000..0761ee7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileDataInteractorTest.kt
@@ -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 com.android.systemui.qs.tiles.impl.onehanded.domain.interactor
+
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.oneHandedModeRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileDataInteractor
+import com.android.wm.shell.onehanded.OneHanded
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class OneHandedModeTileDataInteractorTest : SysuiTestCase() {
+
+    private val kosmos = Kosmos()
+    private val testUser = UserHandle.of(1)!!
+    private val oneHandedModeRepository = kosmos.oneHandedModeRepository
+    private val underTest: OneHandedModeTileDataInteractor =
+        OneHandedModeTileDataInteractor(oneHandedModeRepository)
+
+    @Test
+    fun availability_matchesController() = runTest {
+        val expectedAvailability = OneHanded.sIsSupportOneHandedMode
+        val availability by collectLastValue(underTest.availability(testUser))
+
+        assertThat(availability).isEqualTo(expectedAvailability)
+    }
+
+    @Test
+    fun data_matchesRepository() = runTest {
+        val lastData by
+            collectLastValue(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))
+        runCurrent()
+        assertThat(lastData!!.isEnabled).isFalse()
+
+        oneHandedModeRepository.setIsEnabled(true, testUser)
+        runCurrent()
+        assertThat(lastData!!.isEnabled).isTrue()
+
+        oneHandedModeRepository.setIsEnabled(false, testUser)
+        runCurrent()
+        assertThat(lastData!!.isEnabled).isFalse()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..3f17d4c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileUserActionInteractorTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.qs.tiles.impl.onehanded.domain.interactor
+
+import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.FakeOneHandedModeRepository
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class OneHandedModeTileUserActionInteractorTest : SysuiTestCase() {
+
+    private val testUser = UserHandle.of(1)
+    private val repository = FakeOneHandedModeRepository()
+    private val inputHandler = FakeQSTileIntentUserInputHandler()
+
+    private val underTest =
+        OneHandedModeTileUserActionInteractor(
+            repository,
+            inputHandler,
+        )
+
+    @Test
+    fun handleClickWhenEnabled() = runTest {
+        val wasEnabled = true
+        repository.setIsEnabled(wasEnabled, testUser)
+
+        underTest.handleInput(
+            QSTileInputTestKtx.click(OneHandedModeTileModel(wasEnabled), testUser)
+        )
+
+        assertThat(repository.isEnabled(testUser).value).isEqualTo(!wasEnabled)
+    }
+
+    @Test
+    fun handleClickWhenDisabled() = runTest {
+        val wasEnabled = false
+        repository.setIsEnabled(wasEnabled, testUser)
+
+        underTest.handleInput(
+            QSTileInputTestKtx.click(OneHandedModeTileModel(wasEnabled), testUser)
+        )
+
+        assertThat(repository.isEnabled(testUser).value).isEqualTo(!wasEnabled)
+    }
+
+    @Test
+    fun handleLongClickWhenDisabled() = runTest {
+        val enabled = false
+
+        underTest.handleInput(
+            QSTileInputTestKtx.longClick(OneHandedModeTileModel(enabled), testUser)
+        )
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            assertThat(it.intent.action).isEqualTo(Settings.ACTION_ONE_HANDED_SETTINGS)
+        }
+    }
+
+    @Test
+    fun handleLongClickWhenEnabled() = runTest {
+        val enabled = true
+
+        underTest.handleInput(
+            QSTileInputTestKtx.longClick(OneHandedModeTileModel(enabled), testUser)
+        )
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            assertThat(it.intent.action).isEqualTo(Settings.ACTION_ONE_HANDED_SETTINGS)
+        }
+    }
+}
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
new file mode 100644
index 0000000..7ef020d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.qs.tiles.impl.onehanded.ui
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tileimpl.SubtitleArrayMapping
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
+import com.android.systemui.qs.tiles.impl.onehanded.qsOneHandedModeTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class OneHandedModeTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val config = kosmos.qsOneHandedModeTileConfig
+    private val subtitleArrayId = SubtitleArrayMapping.getSubtitleId(config.tileSpec.spec)
+    private val subtitleArray by lazy { context.resources.getStringArray(subtitleArrayId) }
+
+    private lateinit var mapper: OneHandedModeTileMapper
+
+    @Before
+    fun setup() {
+        mapper =
+            OneHandedModeTileMapper(
+                context.orCreateTestableResources
+                    .apply {
+                        addOverride(
+                            com.android.internal.R.drawable.ic_qs_one_handed_mode,
+                            TestStubDrawable()
+                        )
+                    }
+                    .resources,
+                context.theme
+            )
+    }
+
+    @Test
+    fun disabledModel() {
+        val inputModel = OneHandedModeTileModel(false)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createOneHandedModeTileState(
+                QSTileState.ActivationState.INACTIVE,
+                subtitleArray[1],
+                com.android.internal.R.drawable.ic_qs_one_handed_mode
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun enabledModel() {
+        val inputModel = OneHandedModeTileModel(true)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createOneHandedModeTileState(
+                QSTileState.ActivationState.ACTIVE,
+                subtitleArray[2],
+                com.android.internal.R.drawable.ic_qs_one_handed_mode
+            )
+        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) },
+            label,
+            activationState,
+            secondaryLabel,
+            setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+            label,
+            null,
+            QSTileState.SideViewIcon.None,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt
new file mode 100644
index 0000000..c41ce2f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.qs.tiles.impl.qr.domain.interactor
+
+import android.content.Intent
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController.Callback
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class QRCodeScannerTileDataInteractorTest : SysuiTestCase() {
+
+    private val testUser = UserHandle.of(1)!!
+    private val testDispatcher = StandardTestDispatcher()
+    private val scope = TestScope(testDispatcher)
+    private val testIntent = mock<Intent>()
+    private val qrCodeScannerController =
+        mock<QRCodeScannerController> {
+            whenever(intent).thenReturn(testIntent)
+            whenever(isAbleToLaunchScannerActivity).thenReturn(false)
+        }
+    private val testAvailableModel = QRCodeScannerTileModel.Available(testIntent)
+    private val testUnavailableModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+    private val underTest: QRCodeScannerTileDataInteractor =
+        QRCodeScannerTileDataInteractor(
+            testDispatcher,
+            scope.backgroundScope,
+            qrCodeScannerController,
+        )
+
+    @Test
+    fun availability_matchesController_cameraNotAvailable() =
+        scope.runTest {
+            val expectedAvailability = false
+            whenever(qrCodeScannerController.isCameraAvailable).thenReturn(false)
+
+            val availability by collectLastValue(underTest.availability(testUser))
+
+            assertThat(availability).isEqualTo(expectedAvailability)
+        }
+
+    @Test
+    fun availability_matchesController_cameraIsAvailable() =
+        scope.runTest {
+            val expectedAvailability = true
+            whenever(qrCodeScannerController.isCameraAvailable).thenReturn(true)
+
+            val availability by collectLastValue(underTest.availability(testUser))
+
+            assertThat(availability).isEqualTo(expectedAvailability)
+        }
+
+    @Test
+    fun data_matchesController() =
+        scope.runTest {
+            val captor = argumentCaptor<Callback>()
+            val lastData by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            runCurrent()
+
+            verify(qrCodeScannerController).addCallback(captor.capture())
+            val callback = captor.value
+
+            assertThat(lastData!!).isEqualTo(testUnavailableModel)
+
+            whenever(qrCodeScannerController.isAbleToLaunchScannerActivity).thenReturn(true)
+            callback.onQRCodeScannerActivityChanged()
+            runCurrent()
+            assertThat(lastData!!).isEqualTo(testAvailableModel)
+
+            whenever(qrCodeScannerController.isAbleToLaunchScannerActivity).thenReturn(false)
+            callback.onQRCodeScannerActivityChanged()
+            runCurrent()
+            assertThat(lastData!!).isEqualTo(testUnavailableModel)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..312f180
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.qs.tiles.impl.qr.domain.interactor
+
+import android.content.Intent
+import android.platform.test.annotations.EnabledOnRavenwood
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.impl.qr.qrCodeScannerTileUserActionInteractor
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class QRCodeScannerTileUserActionInteractorTest : SysuiTestCase() {
+    val kosmos = Kosmos()
+    private val inputHandler = kosmos.qsTileIntentUserInputHandler
+    private val underTest = kosmos.qrCodeScannerTileUserActionInteractor
+    private val intent = mock<Intent>()
+
+    @Test
+    fun handleClick_available() = runTest {
+        val inputModel = QRCodeScannerTileModel.Available(intent)
+
+        underTest.handleInput(QSTileInputTestKtx.click(inputModel))
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            intent
+        }
+    }
+
+    @Test
+    fun handleClick_temporarilyUnavailable() = runTest {
+        val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+        underTest.handleInput(QSTileInputTestKtx.click(inputModel))
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs()
+    }
+
+    @Test
+    fun handleLongClick_available() = runTest {
+        val inputModel = QRCodeScannerTileModel.Available(intent)
+
+        underTest.handleInput(QSTileInputTestKtx.longClick(inputModel))
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs()
+    }
+
+    @Test
+    fun handleLongClick_temporarilyUnavailable() = runTest {
+        val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+        underTest.handleInput(QSTileInputTestKtx.longClick(inputModel))
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs()
+    }
+}
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
new file mode 100644
index 0000000..d26a213
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.qs.tiles.impl.qr.ui
+
+import android.content.Intent
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.impl.qr.qsQRCodeScannerTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class QRCodeScannerTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val config = kosmos.qsQRCodeScannerTileConfig
+
+    private lateinit var mapper: QRCodeScannerTileMapper
+
+    @Before
+    fun setup() {
+        mapper =
+            QRCodeScannerTileMapper(
+                context.orCreateTestableResources
+                    .apply {
+                        addOverride(
+                            com.android.systemui.res.R.drawable.ic_qr_code_scanner,
+                            TestStubDrawable()
+                        )
+                    }
+                    .resources,
+                context.theme
+            )
+    }
+
+    @Test
+    fun availableModel() {
+        val mockIntent = mock<Intent>()
+        val inputModel = QRCodeScannerTileModel.Available(mockIntent)
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createQRCodeScannerTileState(
+                QSTileState.ActivationState.INACTIVE,
+                null,
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun temporarilyUnavailableModel() {
+        val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+        val outputState = mapper.map(config, inputModel)
+
+        val expectedState =
+            createQRCodeScannerTileState(
+                QSTileState.ActivationState.UNAVAILABLE,
+                context.getString(
+                    com.android.systemui.res.R.string.qr_code_scanner_updating_secondary_label
+                )
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    private fun createQRCodeScannerTileState(
+        activationState: QSTileState.ActivationState,
+        secondaryLabel: String?,
+    ): QSTileState {
+        val label = context.getString(com.android.systemui.res.R.string.qr_code_scanner_title)
+        return QSTileState(
+            {
+                Icon.Loaded(
+                    context.getDrawable(com.android.systemui.res.R.drawable.ic_qr_code_scanner)!!,
+                    null
+                )
+            },
+            label,
+            activationState,
+            secondaryLabel,
+            setOf(QSTileState.UserAction.CLICK),
+            label,
+            null,
+            QSTileState.SideViewIcon.Chevron,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 179ba42..0b55bef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -35,7 +35,10 @@
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.data.repository.mediaFilterRepository
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
+import com.android.systemui.media.controls.shared.model.MediaData
 import com.android.systemui.qs.FooterActionsController
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
@@ -100,7 +103,7 @@
                 footerActionsViewModelFactory = footerActionsViewModelFactory,
                 footerActionsController = footerActionsController,
                 sceneBackInteractor = sceneBackInteractor,
-                mediaDataManager = mediaDataManager,
+                mediaCarouselInteractor = kosmos.mediaCarouselInteractor,
             )
     }
 
@@ -236,20 +239,34 @@
     }
 
     @Test
-    fun hasMedia_mediaVisible() {
+    fun addAndRemoveMedia_mediaVisibilityIsUpdated() =
         testScope.runTest {
-            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+            kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
+            val isMediaVisible by collectLastValue(underTest.isMediaVisible)
+            val userMedia = MediaData(active = true)
 
-            assertThat(underTest.isMediaVisible()).isTrue()
+            assertThat(isMediaVisible).isFalse()
+
+            kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+
+            assertThat(isMediaVisible).isTrue()
+
+            kosmos.mediaFilterRepository.removeSelectedUserMediaEntry(userMedia.instanceId)
+
+            assertThat(isMediaVisible).isFalse()
         }
-    }
 
     @Test
-    fun doesNotHaveMedia_mediaNotVisible() {
+    fun addInactiveMedia_mediaVisibilityIsUpdated() =
         testScope.runTest {
-            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(false)
+            kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
+            val isMediaVisible by collectLastValue(underTest.isMediaVisible)
+            val userMedia = MediaData(active = false)
 
-            assertThat(underTest.isMediaVisible()).isFalse()
+            assertThat(isMediaVisible).isFalse()
+
+            kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+
+            assertThat(isMediaVisible).isTrue()
         }
-    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 4cb8bf8..9e7e766 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -52,6 +52,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
@@ -209,7 +210,7 @@
                 qsSceneAdapter = qsFlexiglassAdapter,
                 notifications = kosmos.notificationsPlaceholderViewModel,
                 brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
-                mediaDataManager = mediaDataManager,
+                mediaCarouselInteractor = kosmos.mediaCarouselInteractor,
                 shadeInteractor = kosmos.shadeInteractor,
                 footerActionsController = kosmos.footerActionsController,
                 footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index 883760c..df30c4b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -70,6 +70,9 @@
 
             underTest.changeScene(Scenes.Shade)
             assertThat(currentScene).isEqualTo(Scenes.Shade)
+
+            underTest.snapToScene(Scenes.QuickSettings)
+            assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
         }
 
     @Test(expected = IllegalStateException::class)
@@ -79,6 +82,13 @@
         underTest.changeScene(Scenes.Shade)
     }
 
+    @Test(expected = IllegalStateException::class)
+    fun snapToScene_noSuchSceneInContainer_throws() {
+        kosmos.sceneKeys = listOf(Scenes.QuickSettings, Scenes.Lockscreen)
+        val underTest = kosmos.sceneContainerRepository
+        underTest.snapToScene(Scenes.Shade)
+    }
+
     @Test
     fun isVisible() =
         testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt
index c75e297..e3108ad 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt
@@ -45,11 +45,11 @@
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val sceneInteractor = kosmos.sceneInteractor
-    private val sceneContainerStartable = kosmos.sceneContainerStartable
-    private val authenticationInteractor = kosmos.authenticationInteractor
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val sceneContainerStartable by lazy { kosmos.sceneContainerStartable }
+    private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
 
-    private val underTest = kosmos.sceneBackInteractor
+    private val underTest by lazy { kosmos.sceneBackInteractor }
 
     @Test
     @EnableSceneContainer
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index c16d522..2fa94ef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -126,6 +126,71 @@
         }
 
     @Test
+    fun snapToScene_toUnknownScene_doesNothing() =
+        testScope.runTest {
+            val sceneKeys =
+                listOf(
+                    Scenes.QuickSettings,
+                    Scenes.Shade,
+                    Scenes.Lockscreen,
+                    Scenes.Gone,
+                    Scenes.Communal,
+                )
+            val navigationDistances =
+                mapOf(
+                    Scenes.Gone to 0,
+                    Scenes.Lockscreen to 0,
+                    Scenes.Communal to 1,
+                    Scenes.Shade to 2,
+                    Scenes.QuickSettings to 3,
+                )
+            kosmos.sceneContainerConfig =
+                SceneContainerConfig(sceneKeys, Scenes.Lockscreen, navigationDistances)
+            underTest = kosmos.sceneInteractor
+            val currentScene by collectLastValue(underTest.currentScene)
+            val previousScene = currentScene
+            assertThat(previousScene).isNotEqualTo(Scenes.Bouncer)
+            underTest.snapToScene(Scenes.Bouncer, "reason")
+            assertThat(currentScene).isEqualTo(previousScene)
+        }
+
+    @Test
+    fun snapToScene() =
+        testScope.runTest {
+            underTest = kosmos.sceneInteractor
+
+            val currentScene by collectLastValue(underTest.currentScene)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+
+            underTest.snapToScene(Scenes.Shade, "reason")
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
+        }
+
+    @Test
+    fun snapToScene_toGoneWhenUnl_doesNotThrow() =
+        testScope.runTest {
+            underTest = kosmos.sceneInteractor
+
+            val currentScene by collectLastValue(underTest.currentScene)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+            runCurrent()
+
+            underTest.snapToScene(Scenes.Gone, "reason")
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
+        }
+
+    @Test(expected = IllegalStateException::class)
+    fun snapToScene_toGoneWhenStillLocked_throws() =
+        testScope.runTest {
+            underTest = kosmos.sceneInteractor
+            underTest.snapToScene(Scenes.Gone, "reason")
+        }
+
+    @Test
     fun sceneChanged_inDataSource() =
         testScope.runTest {
             underTest = kosmos.sceneInteractor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 93465c8..677477d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -437,12 +437,12 @@
             runCurrent()
             assertThat(
                     sysUiState.flags and
-                        QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0
+                        QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0L
                 )
                 .isTrue()
             assertThat(
                     sysUiState.flags and
-                        QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0
+                        QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0L
                 )
                 .isFalse()
 
@@ -450,12 +450,12 @@
             runCurrent()
             assertThat(
                     sysUiState.flags and
-                        QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0
+                        QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0L
                 )
                 .isFalse()
             assertThat(
                     sysUiState.flags and
-                        QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0
+                        QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0L
                 )
                 .isTrue()
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
new file mode 100644
index 0000000..545a0c7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.viewmodel
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+@EnableSceneContainer
+class GoneSceneViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val shadeRepository by lazy { kosmos.shadeRepository }
+    private lateinit var underTest: GoneSceneViewModel
+
+    @Before
+    fun setUp() {
+        underTest =
+            GoneSceneViewModel(
+                applicationScope = testScope.backgroundScope,
+                shadeInteractor = kosmos.shadeInteractor,
+            )
+    }
+
+    @Test
+    fun downTransitionKey_splitShadeEnabled_isGoneToSplitShade() =
+        testScope.runTest {
+            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            shadeRepository.setShadeMode(ShadeMode.Split)
+            runCurrent()
+
+            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.transitionKey)
+                .isEqualTo(ToSplitShade)
+        }
+
+    @Test
+    fun downTransitionKey_splitShadeDisabled_isNull() =
+        testScope.runTest {
+            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            shadeRepository.setShadeMode(ShadeMode.Single)
+            runCurrent()
+
+            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.transitionKey).isNull()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
index e11a8f1..851b7b9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
@@ -52,9 +52,9 @@
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
-    private val sceneInteractor = kosmos.sceneInteractor
-    private val shadeAnimationInteractor = kosmos.shadeAnimationInteractor
+    private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val shadeAnimationInteractor by lazy { kosmos.shadeAnimationInteractor }
     private val transitionState =
         MutableStateFlow<ObservableTransitionState>(
             ObservableTransitionState.Idle(Scenes.Lockscreen)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
index aa0ca18..78c4def 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
@@ -60,7 +60,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class ShadeInteractorImplTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class ShadeInteractorImplTest(flags: FlagsParameterization) : SysuiTestCase() {
     val kosmos = testKosmos()
     val testScope = kosmos.testScope
     val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
@@ -85,7 +85,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
index 44c9695..cecc70c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
@@ -60,7 +60,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class ShadeStartableTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class ShadeStartableTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val shadeInteractor by lazy { kosmos.shadeInteractor }
@@ -80,7 +80,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 5312ad8..482dc5d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -32,13 +32,17 @@
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.data.repository.mediaFilterRepository
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
+import com.android.systemui.media.controls.shared.model.MediaData
 import com.android.systemui.qs.footerActionsController
 import com.android.systemui.qs.footerActionsViewModelFactory
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
 import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -49,7 +53,6 @@
 import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
 import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
 import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import java.util.Locale
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -93,7 +96,7 @@
                 qsSceneAdapter = qsSceneAdapter,
                 notifications = kosmos.notificationsPlaceholderViewModel,
                 brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
-                mediaDataManager = mediaDataManager,
+                mediaCarouselInteractor = kosmos.mediaCarouselInteractor,
                 shadeInteractor = kosmos.shadeInteractor,
                 footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
                 footerActionsController = kosmos.footerActionsController,
@@ -159,6 +162,27 @@
         }
 
     @Test
+    fun upTransitionKey_splitShadeEnabled_isGoneToSplitShade() =
+        testScope.runTest {
+            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            shadeRepository.setShadeMode(ShadeMode.Split)
+            runCurrent()
+
+            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.transitionKey)
+                .isEqualTo(ToSplitShade)
+        }
+
+    @Test
+    fun upTransitionKey_splitShadeDisable_isNull() =
+        testScope.runTest {
+            val destinationScenes by collectLastValue(underTest.destinationScenes)
+            shadeRepository.setShadeMode(ShadeMode.Single)
+            runCurrent()
+
+            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.transitionKey).isNull()
+        }
+
+    @Test
     fun isClickable_deviceUnlocked_false() =
         testScope.runTest {
             val isClickable by collectLastValue(underTest.isClickable)
@@ -200,19 +224,20 @@
         }
 
     @Test
-    fun hasActiveMedia_mediaVisible() =
+    fun addAndRemoveMedia_mediaVisibilityisUpdated() =
         testScope.runTest {
-            whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
+            val isMediaVisible by collectLastValue(underTest.isMediaVisible)
+            val userMedia = MediaData(active = true)
 
-            assertThat(underTest.isMediaVisible()).isTrue()
-        }
+            assertThat(isMediaVisible).isFalse()
 
-    @Test
-    fun doesNotHaveActiveMedia_mediaNotVisible() =
-        testScope.runTest {
-            whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false)
+            kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
 
-            assertThat(underTest.isMediaVisible()).isFalse()
+            assertThat(isMediaVisible).isTrue()
+
+            kosmos.mediaFilterRepository.removeSelectedUserMediaEntry(userMedia.instanceId)
+
+            assertThat(isMediaVisible).isFalse()
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
new file mode 100644
index 0000000..8cb811d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
@@ -0,0 +1,117 @@
+/*
+ * 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
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.service.notification.NotificationListenerService
+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.dump.dumpManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.mockNotifCollection
+import com.android.systemui.statusbar.notification.collection.notifPipeline
+import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider
+import com.android.systemui.testKosmos
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationMediaManagerTest : SysuiTestCase() {
+
+    private val KEY = "KEY"
+
+    private val kosmos = testKosmos()
+    private val visibilityProvider = kosmos.notificationVisibilityProvider
+    private val notifPipeline = kosmos.notifPipeline
+    private val notifCollection = kosmos.mockNotifCollection
+    private val dumpManager = kosmos.dumpManager
+    private val mediaDataManager = mock<MediaDataManager>()
+    private val backgroundExecutor = FakeExecutor(FakeSystemClock())
+
+    private var listenerCaptor = argumentCaptor<MediaDataManager.Listener>()
+
+    private lateinit var notificationMediaManager: NotificationMediaManager
+
+    @Before
+    fun setup() {
+        notificationMediaManager =
+            NotificationMediaManager(
+                context,
+                visibilityProvider,
+                notifPipeline,
+                notifCollection,
+                mediaDataManager,
+                dumpManager,
+                backgroundExecutor,
+            )
+
+        verify(mediaDataManager).addListener(listenerCaptor.capture())
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS)
+    fun mediaDataRemoved_userInitiated_dismissNotif() {
+        val notifEntryCaptor = argumentCaptor<NotificationEntry>()
+        val notifEntry = mock<NotificationEntry>()
+        whenever(notifEntry.key).thenReturn(KEY)
+        whenever(notifEntry.ranking).thenReturn(NotificationListenerService.Ranking())
+        whenever(notifPipeline.allNotifs).thenReturn(listOf(notifEntry))
+
+        listenerCaptor.lastValue.onMediaDataRemoved(KEY, true)
+
+        verify(notifCollection).dismissNotification(notifEntryCaptor.capture(), any())
+        assertThat(notifEntryCaptor.lastValue.key).isEqualTo(KEY)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS)
+    fun mediaDataRemoved_notUserInitiated_doesNotDismissNotif() {
+        listenerCaptor.lastValue.onMediaDataRemoved(KEY, false)
+
+        verify(notifCollection, never()).dismissNotification(any(), any())
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS)
+    fun mediaDataRemoved_notUserInitiated_flagOff_dismissNotif() {
+        val notifEntryCaptor = argumentCaptor<NotificationEntry>()
+        val notifEntry = mock<NotificationEntry>()
+        whenever(notifEntry.key).thenReturn(KEY)
+        whenever(notifEntry.ranking).thenReturn(NotificationListenerService.Ranking())
+        whenever(notifPipeline.allNotifs).thenReturn(listOf(notifEntry))
+
+        listenerCaptor.lastValue.onMediaDataRemoved(KEY, false)
+
+        verify(notifCollection).dismissNotification(notifEntryCaptor.capture(), any())
+        assertThat(notifEntryCaptor.lastValue.key).isEqualTo(KEY)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index d353a62..f06e04b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -33,7 +33,10 @@
 import com.android.systemui.flags.parameterizeSceneContainerFlag
 import com.android.systemui.jank.interactionJankMonitor
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -65,11 +68,11 @@
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
 @TestableLooper.RunWithLooper
-class StatusBarStateControllerImplTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTestCase() {
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-
+    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     private val mockDarkAnimator = mock<ObjectAnimator>()
 
     private lateinit var underTest: StatusBarStateControllerImpl
@@ -84,7 +87,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
@@ -98,6 +101,7 @@
                     uiEventLogger,
                     { kosmos.interactionJankMonitor },
                     JavaAdapter(testScope.backgroundScope),
+                    { kosmos.keyguardTransitionInteractor },
                     { kosmos.shadeInteractor },
                     { kosmos.deviceUnlockedInteractor },
                     { kosmos.sceneInteractor },
@@ -330,4 +334,25 @@
             assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
             assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)
         }
+
+    @Test
+    fun leaveOpenOnKeyguard_whenGone_isFalse() =
+        testScope.runTest {
+            underTest.start()
+            underTest.setLeaveOpenOnKeyguardHide(true)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.LOCKSCREEN,
+                testScope = testScope,
+            )
+            assertThat(underTest.leaveOpenOnKeyguardHide()).isEqualTo(true)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                testScope = testScope,
+            )
+            assertThat(underTest.leaveOpenOnKeyguardHide()).isEqualTo(false)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt
new file mode 100644
index 0000000..35e4047
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt
@@ -0,0 +1,249 @@
+/*
+ * 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 androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.notifications.ui.composable.NotificationScrimNestedScrollConnection
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationScrimNestedScrollConnectionTest : SysuiTestCase() {
+    private var isStarted = false
+    private var scrimOffset = 0f
+    private var contentHeight = 0f
+    private var isCurrentGestureOverscroll = false
+
+    private val scrollConnection =
+        NotificationScrimNestedScrollConnection(
+            scrimOffset = { scrimOffset },
+            snapScrimOffset = { _ -> },
+            animateScrimOffset = { _ -> },
+            minScrimOffset = { MIN_SCRIM_OFFSET },
+            maxScrimOffset = MAX_SCRIM_OFFSET,
+            contentHeight = { contentHeight },
+            minVisibleScrimHeight = { MIN_VISIBLE_SCRIM_HEIGHT },
+            isCurrentGestureOverscroll = { isCurrentGestureOverscroll },
+            onStart = { isStarted = true },
+            onStop = { isStarted = false },
+        )
+
+    @Test
+    fun onScrollUp_canStartPreScroll_contentNotExpanded_ignoreScroll() = runTest {
+        contentHeight = COLLAPSED_CONTENT_HEIGHT
+
+        val offsetConsumed =
+            scrollConnection.onPreScroll(
+                available = Offset(x = 0f, y = -1f),
+                source = NestedScrollSource.Drag,
+            )
+
+        assertThat(offsetConsumed).isEqualTo(Offset.Zero)
+        assertThat(isStarted).isEqualTo(false)
+    }
+
+    @Test
+    fun onScrollUp_canStartPreScroll_contentExpandedAtMinOffset_ignoreScroll() = runTest {
+        contentHeight = EXPANDED_CONTENT_HEIGHT
+        scrimOffset = MIN_SCRIM_OFFSET
+
+        val offsetConsumed =
+            scrollConnection.onPreScroll(
+                available = Offset(x = 0f, y = -1f),
+                source = NestedScrollSource.Drag,
+            )
+
+        assertThat(offsetConsumed).isEqualTo(Offset.Zero)
+        assertThat(isStarted).isEqualTo(false)
+    }
+
+    @Test
+    fun onScrollUp_canStartPreScroll_contentExpanded_consumeScroll() = runTest {
+        contentHeight = EXPANDED_CONTENT_HEIGHT
+
+        val availableOffset = Offset(x = 0f, y = -1f)
+        val offsetConsumed =
+            scrollConnection.onPreScroll(
+                available = availableOffset,
+                source = NestedScrollSource.Drag,
+            )
+
+        assertThat(offsetConsumed).isEqualTo(availableOffset)
+        assertThat(isStarted).isEqualTo(true)
+    }
+
+    @Test
+    fun onScrollUp_canStartPreScroll_contentExpanded_consumeScrollWithRemainder() = runTest {
+        contentHeight = EXPANDED_CONTENT_HEIGHT
+        scrimOffset = MIN_SCRIM_OFFSET + 1
+
+        val availableOffset = Offset(x = 0f, y = -2f)
+        val consumableOffset = Offset(x = 0f, y = -1f)
+        val offsetConsumed =
+            scrollConnection.onPreScroll(
+                available = availableOffset,
+                source = NestedScrollSource.Drag,
+            )
+
+        assertThat(offsetConsumed).isEqualTo(consumableOffset)
+        assertThat(isStarted).isEqualTo(true)
+    }
+
+    @Test
+    fun onScrollUp_canStartPostScroll_ignoreScroll() = runTest {
+        val offsetConsumed =
+            scrollConnection.onPostScroll(
+                consumed = Offset.Zero,
+                available = Offset(x = 0f, y = -1f),
+                source = NestedScrollSource.Drag,
+            )
+
+        assertThat(offsetConsumed).isEqualTo(Offset.Zero)
+        assertThat(isStarted).isEqualTo(false)
+    }
+
+    @Test
+    fun onScrollDown_canStartPreScroll_ignoreScroll() = runTest {
+        val offsetConsumed =
+            scrollConnection.onPreScroll(
+                available = Offset(x = 0f, y = 1f),
+                source = NestedScrollSource.Drag,
+            )
+
+        assertThat(offsetConsumed).isEqualTo(Offset.Zero)
+        assertThat(isStarted).isEqualTo(false)
+    }
+
+    @Test
+    fun onScrollDown_canStartPostScroll_consumeScroll() = runTest {
+        scrimOffset = MIN_SCRIM_OFFSET
+
+        val availableOffset = Offset(x = 0f, y = 1f)
+        val offsetConsumed =
+            scrollConnection.onPostScroll(
+                consumed = Offset.Zero,
+                available = availableOffset,
+                source = NestedScrollSource.Drag
+            )
+
+        assertThat(offsetConsumed).isEqualTo(availableOffset)
+        assertThat(isStarted).isEqualTo(true)
+    }
+
+    @Test
+    fun onScrollDown_canStartPostScroll_consumeScrollWithRemainder() = runTest {
+        scrimOffset = MAX_SCRIM_OFFSET - 1
+
+        val availableOffset = Offset(x = 0f, y = 2f)
+        val consumableOffset = Offset(x = 0f, y = 1f)
+        val offsetConsumed =
+            scrollConnection.onPostScroll(
+                consumed = Offset.Zero,
+                available = availableOffset,
+                source = NestedScrollSource.Drag
+            )
+
+        assertThat(offsetConsumed).isEqualTo(consumableOffset)
+        assertThat(isStarted).isEqualTo(true)
+    }
+
+    @Test
+    fun canStartPostScroll_atMaxOffset_ignoreScroll() = runTest {
+        scrimOffset = MAX_SCRIM_OFFSET
+
+        val offsetConsumed =
+            scrollConnection.onPostScroll(
+                consumed = Offset.Zero,
+                available = Offset(x = 0f, y = 1f),
+                source = NestedScrollSource.Drag
+            )
+
+        assertThat(offsetConsumed).isEqualTo(Offset.Zero)
+        assertThat(isStarted).isEqualTo(false)
+    }
+
+    @Test
+    fun canStartPostScroll_externalOverscrollGesture_startButIgnoreScroll() = runTest {
+        scrimOffset = MAX_SCRIM_OFFSET
+        isCurrentGestureOverscroll = true
+
+        val offsetConsumed =
+            scrollConnection.onPostScroll(
+                consumed = Offset.Zero,
+                available = Offset(x = 0f, y = 1f),
+                source = NestedScrollSource.Drag
+            )
+
+        assertThat(offsetConsumed).isEqualTo(Offset.Zero)
+        assertThat(isStarted).isEqualTo(true)
+    }
+
+    @Test
+    fun canContinueScroll_inBetweenMinMaxOffset_true() = runTest {
+        scrimOffset = (MIN_SCRIM_OFFSET + MAX_SCRIM_OFFSET) / 2f
+        contentHeight = EXPANDED_CONTENT_HEIGHT
+        scrollConnection.onPreScroll(
+            available = Offset(x = 0f, y = -1f),
+            source = NestedScrollSource.Drag
+        )
+
+        assertThat(isStarted).isEqualTo(true)
+
+        scrollConnection.onPreScroll(
+            available = Offset(x = 0f, y = 1f),
+            source = NestedScrollSource.Drag
+        )
+
+        assertThat(isStarted).isEqualTo(true)
+    }
+
+    @Test
+    fun canContinueScroll_atMaxOffset_false() = runTest {
+        scrimOffset = MAX_SCRIM_OFFSET
+        contentHeight = EXPANDED_CONTENT_HEIGHT
+        scrollConnection.onPreScroll(
+            available = Offset(x = 0f, y = -1f),
+            source = NestedScrollSource.Drag
+        )
+
+        assertThat(isStarted).isEqualTo(true)
+
+        scrollConnection.onPreScroll(
+            available = Offset(x = 0f, y = 1f),
+            source = NestedScrollSource.Drag
+        )
+
+        assertThat(isStarted).isEqualTo(false)
+    }
+
+    companion object {
+        const val MIN_SCRIM_OFFSET = -100f
+        const val MAX_SCRIM_OFFSET = 0f
+
+        const val EXPANDED_CONTENT_HEIGHT = 200f
+        const val COLLAPSED_CONTENT_HEIGHT = 40f
+
+        const val MIN_VISIBLE_SCRIM_HEIGHT = 50f
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
similarity index 62%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
index c5d7e1f..2853264 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
@@ -23,9 +23,8 @@
 import android.app.NotificationManager.IMPORTANCE_LOW
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.collection.GroupEntry
@@ -44,25 +43,29 @@
 import com.android.systemui.statusbar.notification.collection.render.NodeController
 import com.android.systemui.statusbar.notification.icon.ConversationIconManager
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifierImpl
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 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.verify
-import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class ConversationCoordinatorTest : SysuiTestCase() {
     // captured listeners and pluggables:
@@ -72,22 +75,20 @@
     private lateinit var peopleComparator: NotifComparator
     private lateinit var beforeRenderListListener: OnBeforeRenderListListener
 
+    private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier
+    private lateinit var peopleAlertingSection: NotifSection
+
     @Mock private lateinit var pipeline: NotifPipeline
     @Mock private lateinit var conversationIconManager: ConversationIconManager
-    @Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier
-    @Mock private lateinit var channel: NotificationChannel
     @Mock private lateinit var headerController: NodeController
-    private lateinit var entry: NotificationEntry
-    private lateinit var entryA: NotificationEntry
-    private lateinit var entryB: NotificationEntry
 
     private lateinit var coordinator: ConversationCoordinator
 
-    @Rule @JvmField public val setFlagsRule = SetFlagsRule()
-
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        peopleNotificationIdentifier =
+            PeopleNotificationIdentifierImpl(mock(), GroupMembershipManagerImpl())
         coordinator =
             ConversationCoordinator(
                 peopleNotificationIdentifier,
@@ -95,7 +96,6 @@
                 HighPriorityProvider(peopleNotificationIdentifier, GroupMembershipManagerImpl()),
                 headerController
             )
-        whenever(channel.isImportantConversation).thenReturn(true)
 
         coordinator.attach(pipeline)
 
@@ -110,49 +110,65 @@
         if (!SortBySectionTimeFlag.isEnabled)
             peopleComparator = peopleAlertingSectioner.comparator!!
 
-        entry = NotificationEntryBuilder().setChannel(channel).build()
+        peopleAlertingSection = NotifSection(peopleAlertingSectioner, 0)
+    }
 
-        val section = NotifSection(peopleAlertingSectioner, 0)
-        entryA =
-            NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("A").build()
-        entryB =
-            NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("B").build()
+    @Test
+    fun priorityPeopleSectionerClaimsOnlyImportantConversations() {
+        val sectioner = coordinator.priorityPeopleSectioner
+        assertTrue(sectioner.isInSection(makeEntryOfPeopleType(TYPE_IMPORTANT_PERSON)))
+        assertFalse(sectioner.isInSection(makeEntryOfPeopleType(TYPE_FULL_PERSON)))
+        assertFalse(sectioner.isInSection(makeEntryOfPeopleType(TYPE_PERSON)))
+        assertFalse(sectioner.isInSection(makeEntryOfPeopleType(TYPE_NON_PERSON)))
+        assertFalse(sectioner.isInSection(NotificationEntryBuilder().build()))
     }
 
     @Test
     fun testPromotesImportantConversations() {
-        // only promote important conversations
-        assertTrue(promoter.shouldPromoteToTopLevel(entry))
+        assertTrue(promoter.shouldPromoteToTopLevel(makeEntryOfPeopleType(TYPE_IMPORTANT_PERSON)))
+        assertFalse(promoter.shouldPromoteToTopLevel(makeEntryOfPeopleType(TYPE_FULL_PERSON)))
+        assertFalse(promoter.shouldPromoteToTopLevel(makeEntryOfPeopleType(TYPE_PERSON)))
+        assertFalse(promoter.shouldPromoteToTopLevel(makeEntryOfPeopleType(TYPE_NON_PERSON)))
         assertFalse(promoter.shouldPromoteToTopLevel(NotificationEntryBuilder().build()))
     }
 
     @Test
     fun testPromotedImportantConversationsMakesSummaryUnimportant() {
-        val altChildA = NotificationEntryBuilder().setTag("A").build()
-        val altChildB = NotificationEntryBuilder().setTag("B").build()
-        val summary = NotificationEntryBuilder().setId(2).setChannel(channel).build()
+        val importantChannel =
+            mock<NotificationChannel>().also {
+                whenever(it.isImportantConversation).thenReturn(true)
+            }
+        val otherChannel =
+            mock<NotificationChannel>().also {
+                whenever(it.isImportantConversation).thenReturn(false)
+            }
+        val importantChild =
+            makeEntryOfPeopleType(TYPE_IMPORTANT_PERSON) { setChannel(importantChannel) }
+        val altChildA =
+            makeEntryOfPeopleType(TYPE_FULL_PERSON) { setChannel(otherChannel).setTag("A") }
+        val altChildB =
+            makeEntryOfPeopleType(TYPE_FULL_PERSON) { setChannel(otherChannel).setTag("B") }
+        val summary =
+            makeEntryOfPeopleType(TYPE_IMPORTANT_PERSON) { setChannel(importantChannel).setId(2) }
         val groupEntry =
             GroupEntryBuilder()
                 .setParent(GroupEntry.ROOT_ENTRY)
                 .setSummary(summary)
-                .setChildren(listOf(entry, altChildA, altChildB))
+                .setChildren(listOf(importantChild, altChildA, altChildB))
                 .build()
-        assertTrue(promoter.shouldPromoteToTopLevel(entry))
+        assertTrue(promoter.shouldPromoteToTopLevel(importantChild))
         assertFalse(promoter.shouldPromoteToTopLevel(altChildA))
         assertFalse(promoter.shouldPromoteToTopLevel(altChildB))
-        NotificationEntryBuilder.setNewParent(entry, GroupEntry.ROOT_ENTRY)
-        GroupEntryBuilder.getRawChildren(groupEntry).remove(entry)
-        beforeRenderListListener.onBeforeRenderList(listOf(entry, groupEntry))
+        NotificationEntryBuilder.setNewParent(importantChild, GroupEntry.ROOT_ENTRY)
+        GroupEntryBuilder.getRawChildren(groupEntry).remove(importantChild)
+        beforeRenderListListener.onBeforeRenderList(listOf(importantChild, groupEntry))
         verify(conversationIconManager).setUnimportantConversations(eq(listOf(summary.key)))
     }
 
     @Test
     fun testInAlertingPeopleSectionWhenTheImportanceIsAtLeastDefault() {
         // GIVEN
-        val alertingEntry =
-            NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_DEFAULT).build()
-        whenever(peopleNotificationIdentifier.getPeopleNotificationType(alertingEntry))
-            .thenReturn(TYPE_PERSON)
+        val alertingEntry = makeEntryOfPeopleType(TYPE_PERSON) { setImportance(IMPORTANCE_DEFAULT) }
 
         // put alerting people notifications in this section
         assertThat(peopleAlertingSectioner.isInSection(alertingEntry)).isTrue()
@@ -162,10 +178,7 @@
     @EnableFlags(Flags.FLAG_SORT_SECTION_BY_TIME)
     fun testInAlertingPeopleSectionWhenTheImportanceIsLowerThanDefault() {
         // GIVEN
-        val silentEntry =
-                NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build()
-        whenever(peopleNotificationIdentifier.getPeopleNotificationType(silentEntry))
-                .thenReturn(TYPE_PERSON)
+        val silentEntry = makeEntryOfPeopleType(TYPE_PERSON) { setImportance(IMPORTANCE_LOW) }
 
         // THEN put silent people notifications in alerting section
         assertThat(peopleAlertingSectioner.isInSection(silentEntry)).isTrue()
@@ -175,10 +188,7 @@
     @DisableFlags(Flags.FLAG_SORT_SECTION_BY_TIME)
     fun testInSilentPeopleSectionWhenTheImportanceIsLowerThanDefault() {
         // GIVEN
-        val silentEntry =
-            NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build()
-        whenever(peopleNotificationIdentifier.getPeopleNotificationType(silentEntry))
-            .thenReturn(TYPE_PERSON)
+        val silentEntry = makeEntryOfPeopleType(TYPE_PERSON) { setImportance(IMPORTANCE_LOW) }
 
         // THEN put silent people notifications in this section
         assertThat(peopleSilentSectioner.isInSection(silentEntry)).isTrue()
@@ -191,18 +201,14 @@
     @Test
     fun testNotInPeopleSection() {
         // GIVEN
-        val entry =
-            NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build()
+        val entry = makeEntryOfPeopleType(TYPE_NON_PERSON) { setImportance(IMPORTANCE_LOW) }
         val importantEntry =
-            NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_HIGH).build()
-        whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry))
-            .thenReturn(TYPE_NON_PERSON)
-        whenever(peopleNotificationIdentifier.getPeopleNotificationType(importantEntry))
-            .thenReturn(TYPE_NON_PERSON)
+            makeEntryOfPeopleType(TYPE_NON_PERSON) { setImportance(IMPORTANCE_HIGH) }
 
         // THEN - only put people notification either silent or alerting
-        if (!SortBySectionTimeFlag.isEnabled)
+        if (!SortBySectionTimeFlag.isEnabled) {
             assertThat(peopleSilentSectioner.isInSection(entry)).isFalse()
+        }
         assertThat(peopleAlertingSectioner.isInSection(importantEntry)).isFalse()
     }
 
@@ -210,22 +216,16 @@
     fun testInAlertingPeopleSectionWhenThereIsAnImportantChild() {
         // GIVEN
         val altChildA =
-            NotificationEntryBuilder().setTag("A").setImportance(IMPORTANCE_DEFAULT).build()
-        val altChildB = NotificationEntryBuilder().setTag("B").setImportance(IMPORTANCE_LOW).build()
-        val summary =
-            NotificationEntryBuilder()
-                .setId(2)
-                .setImportance(IMPORTANCE_LOW)
-                .setChannel(channel)
-                .build()
+            makeEntryOfPeopleType(TYPE_NON_PERSON) { setTag("A").setImportance(IMPORTANCE_DEFAULT) }
+        val altChildB =
+            makeEntryOfPeopleType(TYPE_NON_PERSON) { setTag("B").setImportance(IMPORTANCE_LOW) }
+        val summary = makeEntryOfPeopleType(TYPE_PERSON) { setId(2).setImportance(IMPORTANCE_LOW) }
         val groupEntry =
             GroupEntryBuilder()
                 .setParent(GroupEntry.ROOT_ENTRY)
                 .setSummary(summary)
                 .setChildren(listOf(altChildA, altChildB))
                 .build()
-        whenever(peopleNotificationIdentifier.getPeopleNotificationType(summary))
-            .thenReturn(TYPE_PERSON)
         // THEN
         assertThat(peopleAlertingSectioner.isInSection(groupEntry)).isTrue()
     }
@@ -233,10 +233,12 @@
     @Test
     @DisableFlags(Flags.FLAG_SORT_SECTION_BY_TIME)
     fun testComparatorPutsImportantPeopleFirst() {
-        whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryA))
-            .thenReturn(TYPE_IMPORTANT_PERSON)
-        whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryB))
-            .thenReturn(TYPE_PERSON)
+        val entryA =
+            makeEntryOfPeopleType(TYPE_IMPORTANT_PERSON) {
+                setSection(peopleAlertingSection).setTag("A")
+            }
+        val entryB =
+            makeEntryOfPeopleType(TYPE_PERSON) { setSection(peopleAlertingSection).setTag("B") }
 
         // only put people notifications in this section
         assertThat(peopleComparator.compare(entryA, entryB)).isEqualTo(-1)
@@ -245,10 +247,10 @@
     @Test
     @DisableFlags(Flags.FLAG_SORT_SECTION_BY_TIME)
     fun testComparatorEquatesPeopleWithSameType() {
-        whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryA))
-            .thenReturn(TYPE_PERSON)
-        whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryB))
-            .thenReturn(TYPE_PERSON)
+        val entryA =
+            makeEntryOfPeopleType(TYPE_PERSON) { setSection(peopleAlertingSection).setTag("A") }
+        val entryB =
+            makeEntryOfPeopleType(TYPE_PERSON) { setSection(peopleAlertingSection).setTag("B") }
 
         // only put people notifications in this section
         assertThat(peopleComparator.compare(entryA, entryB)).isEqualTo(0)
@@ -259,4 +261,23 @@
     fun testNoSecondarySortForConversations() {
         assertThat(peopleAlertingSectioner.comparator).isNull()
     }
+
+    private fun makeEntryOfPeopleType(
+        @PeopleNotificationType type: Int,
+        buildBlock: NotificationEntryBuilder.() -> Unit = {}
+    ): NotificationEntry {
+        val channel: NotificationChannel = mock()
+        whenever(channel.isImportantConversation).thenReturn(type == TYPE_IMPORTANT_PERSON)
+        val entry =
+            NotificationEntryBuilder()
+                .updateRanking {
+                    it.setIsConversation(type != TYPE_NON_PERSON)
+                    it.setShortcutInfo(if (type >= TYPE_FULL_PERSON) mock() else null)
+                    it.setChannel(channel)
+                }
+                .also(buildBlock)
+                .build()
+        assertEquals(type, peopleNotificationIdentifier.getPeopleNotificationType(entry))
+        return entry
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index 50ae985..ce134e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -31,9 +31,9 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.compose.animation.scene.ObservableTransitionState;
@@ -57,6 +57,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.kotlin.JavaAdapter;
@@ -75,7 +76,7 @@
 import org.mockito.verification.VerificationMode;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class VisualStabilityCoordinatorTest extends SysuiTestCase {
 
@@ -86,6 +87,7 @@
     @Mock private WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock private StatusBarStateController mStatusBarStateController;
     @Mock private Pluggable.PluggableListener<NotifStabilityManager> mInvalidateListener;
+    @Mock private SeenNotificationsInteractor mSeenNotificationsInteractor;
     @Mock private HeadsUpManager mHeadsUpManager;
     @Mock private VisibilityLocationProvider mVisibilityLocationProvider;
     @Mock private VisualStabilityProvider mVisualStabilityProvider;
@@ -121,6 +123,7 @@
                 mHeadsUpManager,
                 mShadeAnimationInteractor,
                 mJavaAdapter,
+                mSeenNotificationsInteractor,
                 mStatusBarStateController,
                 mVisibilityLocationProvider,
                 mVisualStabilityProvider,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
similarity index 76%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index 78b7615..9367a93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 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.
@@ -18,116 +18,92 @@
 
 import android.graphics.Rect
 import android.graphics.drawable.Icon
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.DarkIconDispatcher
-import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.data.repository.fakePowerRepository
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.power.shared.model.WakefulnessState
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
-import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationIconViewStateRepository
-import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.headsUpNotificationIconViewStateRepository
 import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher
-import com.android.systemui.statusbar.phone.data.repository.FakeDarkIconRepository
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
-import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.statusbar.phone.data.repository.fakeDarkIconRepository
+import com.android.systemui.statusbar.phone.dozeParameters
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.ui.isAnimating
 import com.android.systemui.util.ui.value
 import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterization) :
+    SysuiTestCase() {
 
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                BiometricsDomainLayerModule::class,
-                UserDomainLayerModule::class,
-            ]
-    )
-    interface TestComponent : SysUITestComponent<NotificationIconContainerStatusBarViewModel> {
-
-        val activeNotificationsRepository: ActiveNotificationListRepository
-        val darkIconRepository: FakeDarkIconRepository
-        val deviceProvisioningRepository: FakeDeviceProvisioningRepository
-        val headsUpViewStateRepository: HeadsUpNotificationIconViewStateRepository
-        val keyguardTransitionRepository: FakeKeyguardTransitionRepository
-        val keyguardRepository: FakeKeyguardRepository
-        val powerRepository: FakePowerRepository
-        val shadeRepository: FakeShadeRepository
-
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-                mocks: TestMocksModule,
-                featureFlags: FakeFeatureFlagsClassicModule,
-            ): TestComponent
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
         }
     }
 
-    private val dozeParams: DozeParameters = mock()
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
 
-    private val testComponent: TestComponent =
-        DaggerNotificationIconContainerStatusBarViewModelTest_TestComponent.factory()
-            .create(
-                test = this,
-                featureFlags =
-                    FakeFeatureFlagsClassicModule {
-                        set(Flags.FULL_SCREEN_USER_SWITCHER, value = false)
-                    },
-                mocks =
-                    TestMocksModule(
-                        dozeParameters = dozeParams,
-                    ),
-            )
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+    private val powerRepository = kosmos.fakePowerRepository
+    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val darkIconRepository = kosmos.fakeDarkIconRepository
+    private val headsUpViewStateRepository = kosmos.headsUpNotificationIconViewStateRepository
+    private val activeNotificationsRepository = kosmos.activeNotificationListRepository
+
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+    private val dozeParams = kosmos.dozeParameters
+
+    lateinit var underTest: NotificationIconContainerStatusBarViewModel
 
     @Before
     fun setup() {
-        testComponent.apply {
-            keyguardRepository.setKeyguardShowing(false)
-            powerRepository.updateWakefulness(
-                rawState = WakefulnessState.AWAKE,
-                lastWakeReason = WakeSleepReason.OTHER,
-                lastSleepReason = WakeSleepReason.OTHER,
-            )
-        }
+        underTest = kosmos.notificationIconContainerStatusBarViewModel
+        keyguardRepository.setKeyguardShowing(false)
+        powerRepository.updateWakefulness(
+            rawState = WakefulnessState.AWAKE,
+            lastWakeReason = WakeSleepReason.OTHER,
+            lastSleepReason = WakeSleepReason.OTHER,
+        )
     }
 
     @Test
     fun animationsEnabled_isFalse_whenDeviceAsleepAndNotPulsing() =
-        testComponent.runTest {
+        testScope.runTest {
             powerRepository.updateWakefulness(
                 rawState = WakefulnessState.ASLEEP,
                 lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -150,7 +126,7 @@
 
     @Test
     fun animationsEnabled_isTrue_whenDeviceAsleepAndPulsing() =
-        testComponent.runTest {
+        testScope.runTest {
             powerRepository.updateWakefulness(
                 rawState = WakefulnessState.ASLEEP,
                 lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -173,7 +149,7 @@
 
     @Test
     fun animationsEnabled_isFalse_whenStartingToSleepAndNotControlScreenOff() =
-        testComponent.runTest {
+        testScope.runTest {
             powerRepository.updateWakefulness(
                 rawState = WakefulnessState.STARTING_TO_SLEEP,
                 lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -194,7 +170,7 @@
 
     @Test
     fun animationsEnabled_isTrue_whenStartingToSleepAndControlScreenOff() =
-        testComponent.runTest {
+        testScope.runTest {
             val animationsEnabled by collectLastValue(underTest.animationsEnabled)
             assertThat(animationsEnabled).isTrue()
 
@@ -218,7 +194,7 @@
 
     @Test
     fun animationsEnabled_isTrue_whenNotAsleep() =
-        testComponent.runTest {
+        testScope.runTest {
             powerRepository.updateWakefulness(
                 rawState = WakefulnessState.AWAKE,
                 lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -236,7 +212,7 @@
 
     @Test
     fun animationsEnabled_isTrue_whenKeyguardIsNotShowing() =
-        testComponent.runTest {
+        testScope.runTest {
             val animationsEnabled by collectLastValue(underTest.animationsEnabled)
 
             keyguardTransitionRepository.sendTransitionStep(
@@ -257,7 +233,7 @@
 
     @Test
     fun iconColors_testsDarkBounds() =
-        testComponent.runTest {
+        testScope.runTest {
             darkIconRepository.darkState.value =
                 SysuiDarkIconDispatcher.DarkChange(
                     emptyList(),
@@ -280,7 +256,7 @@
 
     @Test
     fun iconColors_staticDrawableColor_notInDarkTintArea() =
-        testComponent.runTest {
+        testScope.runTest {
             darkIconRepository.darkState.value =
                 SysuiDarkIconDispatcher.DarkChange(
                     listOf(Rect(0, 0, 5, 5)),
@@ -295,7 +271,7 @@
 
     @Test
     fun iconColors_notInDarkTintArea() =
-        testComponent.runTest {
+        testScope.runTest {
             darkIconRepository.darkState.value =
                 SysuiDarkIconDispatcher.DarkChange(
                     listOf(Rect(0, 0, 5, 5)),
@@ -309,9 +285,9 @@
 
     @Test
     fun isolatedIcon_animateOnAppear_shadeCollapsed() =
-        testComponent.runTest {
+        testScope.runTest {
             val icon: Icon = mock()
-            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeTestUtil.setShadeExpansion(0f)
             activeNotificationsRepository.activeNotifications.value =
                 ActiveNotificationsStore.Builder()
                     .apply {
@@ -336,9 +312,9 @@
 
     @Test
     fun isolatedIcon_dontAnimateOnAppear_shadeExpanded() =
-        testComponent.runTest {
+        testScope.runTest {
             val icon: Icon = mock()
-            shadeRepository.setLegacyShadeExpansion(.5f)
+            shadeTestUtil.setShadeExpansion(.5f)
             activeNotificationsRepository.activeNotifications.value =
                 ActiveNotificationsStore.Builder()
                     .apply {
@@ -363,7 +339,7 @@
 
     @Test
     fun isolatedIcon_updateWhenIconDataChanges() =
-        testComponent.runTest {
+        testScope.runTest {
             val icon: Icon = mock()
             val isolatedIcon by collectLastValue(underTest.isolatedIcon)
             runCurrent()
@@ -390,7 +366,7 @@
 
     @Test
     fun isolatedIcon_lastMessageIsFromReply_notNull() =
-        testComponent.runTest {
+        testScope.runTest {
             val icon: Icon = mock()
             headsUpViewStateRepository.isolatedNotification.value = "notif1"
             activeNotificationsRepository.activeNotifications.value =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 7ac549a..cc5df74 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -20,12 +20,13 @@
 
 import android.app.NotificationManager.Policy
 import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
 import android.provider.Settings
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -33,7 +34,7 @@
 import com.android.systemui.power.data.repository.fakePowerRepository
 import com.android.systemui.power.shared.model.WakefulnessState
 import com.android.systemui.res.R
-import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.statusbar.data.repository.fakeRemoteInputRepository
 import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
@@ -56,11 +57,13 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 @EnableFlags(FooterViewRefactor.FLAG_NAME)
-class NotificationListViewModelTest : SysuiTestCase() {
+class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
             fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
@@ -72,16 +75,30 @@
     private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
     private val fakePowerRepository = kosmos.fakePowerRepository
     private val fakeRemoteInputRepository = kosmos.fakeRemoteInputRepository
-    private val fakeShadeRepository = kosmos.fakeShadeRepository
     private val fakeUserSetupRepository = kosmos.fakeUserSetupRepository
     private val headsUpRepository = kosmos.headsUpNotificationRepository
     private val zenModeRepository = kosmos.zenModeRepository
 
-    val underTest = kosmos.notificationListViewModel
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+    private lateinit var underTest: NotificationListViewModel
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        underTest = kosmos.notificationListViewModel
     }
 
     @Test
@@ -163,7 +180,7 @@
             // WHEN has no notifs
             activeNotificationListRepository.setActiveNotifs(count = 0)
             // AND quick settings are expanded
-            fakeShadeRepository.legacyQsFullscreen.value = true
+            shadeTestUtil.setQsFullscreen(true)
             runCurrent()
 
             // THEN empty shade is not visible
@@ -178,9 +195,10 @@
             // WHEN has no notifs
             activeNotificationListRepository.setActiveNotifs(count = 0)
             // AND quick settings are expanded
-            fakeShadeRepository.setQsExpansion(1f)
-            // AND split shade is enabled
+            shadeTestUtil.setQsExpansion(1f)
+            // AND split shade is expanded
             overrideResource(R.bool.config_use_split_notification_shade, true)
+            shadeTestUtil.setShadeExpansion(1f)
             fakeConfigurationController.notifyConfigurationChanged()
             runCurrent()
 
@@ -290,7 +308,7 @@
             activeNotificationListRepository.setActiveNotifs(count = 2)
             // AND shade is open
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             runCurrent()
 
             // THEN footer is visible
@@ -306,7 +324,7 @@
             activeNotificationListRepository.setActiveNotifs(count = 2)
             // AND shade is open on lockscreen
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
-            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             runCurrent()
 
             // THEN footer is visible
@@ -337,7 +355,7 @@
             activeNotificationListRepository.setActiveNotifs(count = 2)
             // AND shade is open
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             // AND user is not set up
             fakeUserSetupRepository.setUserSetUp(false)
             runCurrent()
@@ -355,7 +373,7 @@
             activeNotificationListRepository.setActiveNotifs(count = 2)
             // AND shade is open
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             // AND device is starting to go to sleep
             fakePowerRepository.updateWakefulness(WakefulnessState.STARTING_TO_SLEEP)
             runCurrent()
@@ -373,10 +391,10 @@
             activeNotificationListRepository.setActiveNotifs(count = 2)
             // AND shade is open
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             // AND quick settings are expanded
-            fakeShadeRepository.setQsExpansion(1f)
-            fakeShadeRepository.legacyQsFullscreen.value = true
+            shadeTestUtil.setQsExpansion(1f)
+            shadeTestUtil.setQsFullscreen(true)
             runCurrent()
 
             // THEN footer is not visible
@@ -390,11 +408,11 @@
 
             // WHEN has notifs
             activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND quick settings are expanded
+            shadeTestUtil.setQsExpansion(1f)
             // AND shade is open
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            fakeShadeRepository.setLegacyShadeExpansion(1f)
-            // AND quick settings are expanded
-            fakeShadeRepository.setQsExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             // AND split shade is enabled
             overrideResource(R.bool.config_use_split_notification_shade, true)
             fakeConfigurationController.notifyConfigurationChanged()
@@ -413,7 +431,7 @@
             activeNotificationListRepository.setActiveNotifs(count = 2)
             // AND shade is open
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             // AND remote input is active
             fakeRemoteInputRepository.isRemoteInputActive.value = true
             runCurrent()
@@ -431,7 +449,7 @@
             activeNotificationListRepository.setActiveNotifs(count = 2)
             // AND shade is open and fully expanded
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             runCurrent()
 
             // THEN footer visibility animates
@@ -447,7 +465,7 @@
             activeNotificationListRepository.setActiveNotifs(count = 2)
             // AND we are on the keyguard
             fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             runCurrent()
 
             // THEN footer visibility does not animate
@@ -461,7 +479,7 @@
 
             // WHEN shade is closed
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            fakeShadeRepository.setLegacyShadeExpansion(0f)
+            shadeTestUtil.setShadeExpansion(0f)
             runCurrent()
 
             // THEN footer is hidden
@@ -475,7 +493,7 @@
 
             // WHEN shade is open
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             runCurrent()
 
             // THEN footer is hidden
@@ -489,8 +507,8 @@
 
             // WHEN QS partially open
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            fakeShadeRepository.setQsExpansion(0.5f)
-            fakeShadeRepository.setLegacyShadeExpansion(0.5f)
+            shadeTestUtil.setQsExpansion(0.5f)
+            shadeTestUtil.setShadeExpansion(0.5f)
             runCurrent()
 
             // THEN footer is hidden
@@ -588,7 +606,7 @@
         testScope.runTest {
             val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
 
-            fakeShadeRepository.setQsExpansion(0.0f)
+            shadeTestUtil.setQsExpansion(0.0f)
             fakeKeyguardRepository.setKeyguardShowing(false)
             runCurrent()
 
@@ -601,7 +619,7 @@
         testScope.runTest {
             val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
 
-            fakeShadeRepository.setQsExpansion(0.0f)
+            shadeTestUtil.setQsExpansion(0.0f)
             fakeKeyguardRepository.setKeyguardShowing(true)
             runCurrent()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
index 1f0812d..ee9fd349 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
@@ -44,13 +44,4 @@
                 collectLastValue(kosmos.notificationStackAppearanceInteractor.shadeScrimBounds)
             assertThat(stackBounds).isEqualTo(bounds)
         }
-
-    @Test
-    fun onStackBoundsChanged() =
-        kosmos.testScope.runTest {
-            underTest.onStackBoundsChanged(top = 5f, bottom = 500f)
-            assertThat(kosmos.notificationStackAppearanceInteractor.stackTop.value).isEqualTo(5f)
-            assertThat(kosmos.notificationStackAppearanceInteractor.stackBottom.value)
-                .isEqualTo(500f)
-        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 3408e06..82e2bb7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.BrokenWithSceneContainer
 import com.android.systemui.flags.DisableSceneContainer
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.Flags
@@ -61,6 +62,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -74,7 +76,7 @@
 @RunWith(ParameterizedAndroidJunit4::class)
 // SharedNotificationContainerViewModel is only bound when FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT is on
 @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-class SharedNotificationContainerViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
 
     companion object {
         @JvmStatic
@@ -88,7 +90,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     val aodBurnInViewModel = mock(AodBurnInViewModel::class.java)
@@ -106,18 +108,25 @@
     val testScope = kosmos.testScope
     val configurationRepository
         get() = kosmos.fakeConfigurationRepository
+
     val keyguardRepository
         get() = kosmos.fakeKeyguardRepository
+
     val keyguardInteractor
         get() = kosmos.keyguardInteractor
+
     val keyguardRootViewModel
         get() = kosmos.keyguardRootViewModel
+
     val keyguardTransitionRepository
         get() = kosmos.fakeKeyguardTransitionRepository
+
     val shadeTestUtil
         get() = kosmos.shadeTestUtil
+
     val sharedNotificationContainerInteractor
         get() = kosmos.sharedNotificationContainerInteractor
+
     val largeScreenHeaderHelper
         get() = kosmos.mockLargeScreenHeaderHelper
 
@@ -654,26 +663,25 @@
             var notificationCount = 10
             val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> notificationCount }
             val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace))
-
+            advanceTimeBy(50L)
             showLockscreen()
 
             overrideResource(R.bool.config_use_split_notification_shade, false)
             configurationRepository.onAnyConfigurationChange()
-            keyguardInteractor.setNotificationContainerBounds(
-                NotificationContainerBounds(top = 1f, bottom = 2f)
-            )
 
             assertThat(maxNotifications).isEqualTo(10)
 
             // Also updates when directly requested (as it would from NotificationStackScrollLayout)
             notificationCount = 25
             sharedNotificationContainerInteractor.notificationStackChanged()
+            advanceTimeBy(50L)
             assertThat(maxNotifications).isEqualTo(25)
 
             // Also ensure another collection starts with the same value. As an example, folding
             // then unfolding will restart the coroutine and it must get the last value immediately.
             val newMaxNotifications by
                 collectLastValue(underTest.getMaxNotifications(calculateSpace))
+            advanceTimeBy(50L)
             assertThat(newMaxNotifications).isEqualTo(25)
         }
 
@@ -683,14 +691,11 @@
             var notificationCount = 10
             val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> notificationCount }
             val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace))
-
+            advanceTimeBy(50L)
             showLockscreen()
 
             overrideResource(R.bool.config_use_split_notification_shade, false)
             configurationRepository.onAnyConfigurationChange()
-            keyguardInteractor.setNotificationContainerBounds(
-                NotificationContainerBounds(top = 1f, bottom = 2f)
-            )
 
             assertThat(maxNotifications).isEqualTo(10)
 
@@ -718,15 +723,13 @@
         testScope.runTest {
             val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> 10 }
             val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace))
+            advanceTimeBy(50L)
 
             // Show lockscreen with shade expanded
             showLockscreenWithShadeExpanded()
 
             overrideResource(R.bool.config_use_split_notification_shade, false)
             configurationRepository.onAnyConfigurationChange()
-            keyguardInteractor.setNotificationContainerBounds(
-                NotificationContainerBounds(top = 1f, bottom = 2f)
-            )
 
             // -1 means No Limit
             assertThat(maxNotifications).isEqualTo(-1)
@@ -819,6 +822,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(330311871)
     fun alphaDoesNotUpdateWhileGoneTransitionIsRunning() =
         testScope.runTest {
             val viewState = ViewStateAccessor()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt
index 1cd12f0..7bc6948 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt
@@ -35,6 +35,7 @@
 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
 import org.mockito.Mockito.verify
@@ -46,30 +47,32 @@
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
 
-    private val sceneContainerRepository = kosmos.sceneContainerRepository
-    private val keyguardInteractor = kosmos.keyguardInteractor
+    lateinit var underTest: DozeServiceHost
 
-    val underTest =
-        kosmos.dozeServiceHost.apply {
-            initialize(
-                /* centralSurfaces = */ mock(),
-                /* statusBarKeyguardViewManager = */ mock(),
-                /* notificationShadeWindowViewController = */ mock(),
-                /* ambientIndicationContainer = */ mock(),
-            )
-        }
+    @Before
+    fun setup() {
+        underTest =
+            kosmos.dozeServiceHost.apply {
+                initialize(
+                    /* centralSurfaces = */ mock(),
+                    /* statusBarKeyguardViewManager = */ mock(),
+                    /* notificationShadeWindowViewController = */ mock(),
+                    /* ambientIndicationContainer = */ mock(),
+                )
+            }
+    }
 
     @Test
     @EnableSceneContainer
     fun startStopDozing() =
         testScope.runTest {
-            val isDozing by collectLastValue(keyguardInteractor.isDozing)
+            val isDozing by collectLastValue(kosmos.keyguardInteractor.isDozing)
 
             // GIVEN a callback is set
             val callback: DozeHost.Callback = mock()
             underTest.addCallback(callback)
             // AND we are on the lock screen
-            sceneContainerRepository.changeScene(Scenes.Lockscreen)
+            kosmos.sceneContainerRepository.changeScene(Scenes.Lockscreen)
             // AND dozing is not requested yet
             assertThat(underTest.dozingRequested).isFalse()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
index 8ce5037..63f19fb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -249,22 +249,40 @@
 
 
     @Test
-    fun testDelete_showingEntryKeyBecomesPreviousHunKey() {
+    fun testDelete_deleteSecondToLastEntry_showingEntryKeyBecomesPreviousHunKey() {
         mAvalancheController.previousHunKey = ""
 
         // Entry is showing
+        val firstEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.headsUpEntryShowing = firstEntry
+
+        // There's another entry waiting to show next
+        val secondEntry = createHeadsUpEntry(id = 1)
+        mAvalancheController.addToNext(secondEntry, runnableMock!!)
+
+        // Delete
+        mAvalancheController.delete(firstEntry, runnableMock, "testLabel")
+
+        // Next entry is shown
+        assertThat(mAvalancheController.previousHunKey).isEqualTo(firstEntry.mEntry!!.key)
+    }
+
+    @Test
+    fun testDelete_deleteLastEntry_previousHunKeyCleared() {
+        mAvalancheController.previousHunKey = "key"
+
+        // Nothing waiting to show
+        mAvalancheController.clearNext()
+
+        // One entry is showing
         val showingEntry = createHeadsUpEntry(id = 0)
         mAvalancheController.headsUpEntryShowing = showingEntry
 
-        // There's another entry waiting to show next
-        val nextEntry = createHeadsUpEntry(id = 1)
-        mAvalancheController.addToNext(nextEntry, runnableMock!!)
-
         // Delete
-        mAvalancheController.delete(showingEntry, runnableMock, "testLabel")
+        mAvalancheController.delete(showingEntry, runnableMock!!, "testLabel")
 
         // Next entry is shown
-        assertThat(mAvalancheController.previousHunKey).isEqualTo(showingEntry.mEntry!!.key)
+        assertThat(mAvalancheController.previousHunKey).isEqualTo("");
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt
index 03a39f8..2d8cd93 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt
@@ -23,9 +23,9 @@
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
-import com.android.systemui.util.kotlin.BooleanFlowOperators.and
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
+import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.not
-import com.android.systemui.util.kotlin.BooleanFlowOperators.or
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -45,21 +45,21 @@
     @Test
     fun and_allTrue_returnsTrue() =
         testScope.runTest {
-            val result by collectLastValue(and(TRUE, TRUE))
+            val result by collectLastValue(allOf(TRUE, TRUE))
             assertThat(result).isTrue()
         }
 
     @Test
     fun and_anyFalse_returnsFalse() =
         testScope.runTest {
-            val result by collectLastValue(and(TRUE, FALSE, TRUE))
+            val result by collectLastValue(allOf(TRUE, FALSE, TRUE))
             assertThat(result).isFalse()
         }
 
     @Test
     fun and_allFalse_returnsFalse() =
         testScope.runTest {
-            val result by collectLastValue(and(FALSE, FALSE, FALSE))
+            val result by collectLastValue(allOf(FALSE, FALSE, FALSE))
             assertThat(result).isFalse()
         }
 
@@ -68,7 +68,7 @@
         testScope.runTest {
             val flow1 = MutableStateFlow(false)
             val flow2 = MutableStateFlow(false)
-            val values by collectValues(and(flow1, flow2))
+            val values by collectValues(allOf(flow1, flow2))
 
             assertThat(values).containsExactly(false)
             flow1.value = true
@@ -81,21 +81,21 @@
     @Test
     fun or_allTrue_returnsTrue() =
         testScope.runTest {
-            val result by collectLastValue(or(TRUE, TRUE))
+            val result by collectLastValue(anyOf(TRUE, TRUE))
             assertThat(result).isTrue()
         }
 
     @Test
     fun or_anyTrue_returnsTrue() =
         testScope.runTest {
-            val result by collectLastValue(or(FALSE, TRUE, FALSE))
+            val result by collectLastValue(anyOf(FALSE, TRUE, FALSE))
             assertThat(result).isTrue()
         }
 
     @Test
     fun or_allFalse_returnsFalse() =
         testScope.runTest {
-            val result by collectLastValue(or(FALSE, FALSE, FALSE))
+            val result by collectLastValue(anyOf(FALSE, FALSE, FALSE))
             assertThat(result).isFalse()
         }
 
@@ -104,7 +104,7 @@
         testScope.runTest {
             val flow1 = MutableStateFlow(false)
             val flow2 = MutableStateFlow(false)
-            val values by collectValues(or(flow1, flow2))
+            val values by collectValues(anyOf(flow1, flow2))
 
             assertThat(values).containsExactly(false)
             flow1.value = true
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
index 675136c..a163ca0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
@@ -36,7 +36,6 @@
 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,19 +46,8 @@
 
     private val kosmos = testKosmos()
 
-    private lateinit var underTest: AudioVolumeInteractor
-
-    @Before
-    fun setup() {
-        with(kosmos) {
-            underTest = AudioVolumeInteractor(audioRepository, notificationsSoundPolicyInteractor)
-
-            audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_NORMAL))
-
-            notificationsSoundPolicyRepository.updateNotificationPolicy()
-            notificationsSoundPolicyRepository.updateZenMode(ZenMode(Settings.Global.ZEN_MODE_OFF))
-        }
-    }
+    private val underTest: AudioVolumeInteractor =
+        with(kosmos) { AudioVolumeInteractor(audioRepository, notificationsSoundPolicyInteractor) }
 
     @Test
     fun setMuted_mutesStream() {
@@ -236,6 +224,55 @@
         }
     }
 
+    @Test
+    fun testReducingVolumeToMin_mutes() =
+        with(kosmos) {
+            testScope.runTest {
+                val audioStreamModel by
+                    collectLastValue(audioRepository.getAudioStream(audioStream))
+                runCurrent()
+
+                underTest.setVolume(audioStream, audioStreamModel!!.minVolume)
+                runCurrent()
+
+                assertThat(audioStreamModel!!.isMuted).isTrue()
+            }
+        }
+
+    @Test
+    fun testIncreasingVolumeFromMin_unmutes() =
+        with(kosmos) {
+            testScope.runTest {
+                val audioStreamModel by
+                    collectLastValue(audioRepository.getAudioStream(audioStream))
+                audioRepository.setMuted(audioStream, true)
+                audioRepository.setVolume(audioStream, audioStreamModel!!.minVolume)
+                runCurrent()
+
+                underTest.setVolume(audioStream, audioStreamModel!!.maxVolume)
+                runCurrent()
+
+                assertThat(audioStreamModel!!.isMuted).isFalse()
+            }
+        }
+
+    @Test
+    fun testUnmutingMinVolume_increasesVolume() =
+        with(kosmos) {
+            testScope.runTest {
+                val audioStreamModel by
+                    collectLastValue(audioRepository.getAudioStream(audioStream))
+                audioRepository.setMuted(audioStream, true)
+                audioRepository.setVolume(audioStream, audioStreamModel!!.minVolume)
+                runCurrent()
+
+                underTest.setMuted(audioStream, false)
+                runCurrent()
+
+                assertThat(audioStreamModel!!.volume).isGreaterThan(audioStreamModel!!.minVolume)
+            }
+        }
+
     private companion object {
         val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt
index dc96139..9a95274 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt
@@ -18,20 +18,13 @@
 
 import android.bluetooth.BluetoothDevice
 import android.net.Uri
+import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.settingslib.bluetooth.CachedBluetoothDevice
-import com.android.settingslib.media.BluetoothMediaDevice
-import com.android.settingslib.media.MediaDevice
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.volume.localMediaRepository
-import com.android.systemui.volume.localMediaRepositoryFactory
 import com.android.systemui.volume.panel.component.anc.FakeSliceFactory
 import com.android.systemui.volume.panel.component.anc.sliceViewManager
 import com.google.common.truth.Truth.assertThat
@@ -41,10 +34,14 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 class AncSliceRepositoryTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
@@ -57,22 +54,23 @@
             val slice = FakeSliceFactory.createSlice(hasError = false, hasSliceItem = true)
             whenever(sliceViewManager.bindSlice(any<Uri>())).thenReturn(slice)
 
-            underTest =
-                AncSliceRepositoryImpl(
-                    localMediaRepositoryFactory,
-                    testScope.testScheduler,
-                    sliceViewManager,
-                )
+            underTest = AncSliceRepositoryImpl(testScope.testScheduler, sliceViewManager)
         }
     }
 
     @Test
-    fun noConnectedDevice_noSlice() {
+    fun connectedDevice_noUri_noSlice() {
         with(kosmos) {
             testScope.runTest {
-                localMediaRepository.updateCurrentConnectedDevice(null)
-
-                val slice by collectLastValue(underTest.ancSlice(1, false, false))
+                val slice by
+                    collectLastValue(
+                        underTest.ancSlice(
+                            device = createMediaDevice(""),
+                            width = 1,
+                            isCollapsed = false,
+                            hideLabel = false,
+                        )
+                    )
                 runCurrent()
 
                 assertThat(slice).isNull()
@@ -81,12 +79,18 @@
     }
 
     @Test
-    fun connectedDevice_sliceReturned() {
+    fun connectedDevice_hasUri_sliceReturned() {
         with(kosmos) {
             testScope.runTest {
-                localMediaRepository.updateCurrentConnectedDevice(createMediaDevice())
-
-                val slice by collectLastValue(underTest.ancSlice(1, false, false))
+                val slice by
+                    collectLastValue(
+                        underTest.ancSlice(
+                            device = createMediaDevice("content://test.slice"),
+                            width = 1,
+                            isCollapsed = false,
+                            hideLabel = false,
+                        )
+                    )
                 runCurrent()
 
                 assertThat(slice).isNotNull()
@@ -94,21 +98,13 @@
         }
     }
 
-    private fun createMediaDevice(sliceUri: String = "content://test.slice"): MediaDevice {
-        val bluetoothDevice: BluetoothDevice = mock {
-            whenever(getMetadata(any()))
-                .thenReturn(
-                    ("<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" +
-                            sliceUri +
-                            "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>")
-                        .toByteArray()
-                )
-        }
-        val cachedBluetoothDevice: CachedBluetoothDevice = mock {
-            whenever(device).thenReturn(bluetoothDevice)
-        }
-        return mock<BluetoothMediaDevice> {
-            whenever(cachedDevice).thenReturn(cachedBluetoothDevice)
-        }
+    private fun createMediaDevice(sliceUri: String): BluetoothDevice = mock {
+        on { getMetadata(any()) }
+            .thenReturn(
+                ("<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" +
+                        sliceUri +
+                        "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>")
+                    .toByteArray()
+            )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteriaTest.kt
index 553aed8..8d052fe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteriaTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteriaTest.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.volume.panel.component.anc.domain
 
+import android.media.AudioManager
 import android.net.Uri
+import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -26,10 +28,13 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.volume.data.repository.audioRepository
+import com.android.systemui.volume.localMediaRepository
 import com.android.systemui.volume.panel.component.anc.FakeSliceFactory
 import com.android.systemui.volume.panel.component.anc.ancSliceInteractor
 import com.android.systemui.volume.panel.component.anc.ancSliceRepository
 import com.android.systemui.volume.panel.component.anc.sliceViewManager
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.TestMediaDevicesFactory
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
@@ -41,6 +46,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 class AncAvailabilityCriteriaTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
@@ -74,6 +80,10 @@
     fun hasSlice_available() {
         with(kosmos) {
             testScope.runTest {
+                audioRepository.setMode(AudioManager.MODE_NORMAL)
+                localMediaRepository.updateCurrentConnectedDevice(
+                    TestMediaDevicesFactory.bluetoothMediaDevice()
+                )
                 ancSliceRepository.putSlice(
                     1,
                     FakeSliceFactory.createSlice(hasError = false, hasSliceItem = true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt
index 81e6ac4..741671e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt
@@ -16,15 +16,21 @@
 
 package com.android.systemui.volume.panel.component.anc.domain.interactor
 
+import android.media.AudioManager
+import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
+import com.android.systemui.volume.data.repository.audioRepository
+import com.android.systemui.volume.localMediaRepository
 import com.android.systemui.volume.panel.component.anc.FakeSliceFactory
+import com.android.systemui.volume.panel.component.anc.ancSliceInteractor
 import com.android.systemui.volume.panel.component.anc.ancSliceRepository
 import com.android.systemui.volume.panel.component.anc.domain.model.AncSlices
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.TestMediaDevicesFactory
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
@@ -36,6 +42,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 class AncSliceInteractorTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
@@ -43,14 +50,12 @@
     private lateinit var underTest: AncSliceInteractor
 
     @Before
-    fun setup() {
-        with(kosmos) {
-            underTest = AncSliceInteractor(ancSliceRepository, testScope.backgroundScope)
-        }
+    fun setUp() {
+        underTest = kosmos.ancSliceInteractor
     }
 
     @Test
-    fun errorSlice_returnsNull() {
+    fun errorSlice_returnsUnavailable() {
         with(kosmos) {
             testScope.runTest {
                 ancSliceRepository.putSlice(
@@ -67,7 +72,7 @@
     }
 
     @Test
-    fun noSliceItem_returnsNull() {
+    fun noSliceItem_returnsUnavailable() {
         with(kosmos) {
             testScope.runTest {
                 ancSliceRepository.putSlice(
@@ -84,9 +89,31 @@
     }
 
     @Test
+    fun sliceItem_noError_noDevice_returnsUnavailable() {
+        with(kosmos) {
+            testScope.runTest {
+                ancSliceRepository.putSlice(
+                    1,
+                    FakeSliceFactory.createSlice(hasError = false, hasSliceItem = true)
+                )
+
+                val slice by collectLastValue(underTest.ancSlices)
+                runCurrent()
+
+                assertThat(slice).isInstanceOf(AncSlices.Unavailable::class.java)
+            }
+        }
+    }
+
+    @Test
     fun sliceItem_noError_returnsSlice() {
         with(kosmos) {
             testScope.runTest {
+                audioRepository.setMode(AudioManager.MODE_NORMAL)
+                localMediaRepository.updateCurrentConnectedDevice(
+                    TestMediaDevicesFactory.bluetoothMediaDevice()
+                )
+
                 ancSliceRepository.putSlice(
                     1,
                     FakeSliceFactory.createSlice(hasError = false, hasSliceItem = true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
index 27a813f..fdea5a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
@@ -32,7 +32,7 @@
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
-import com.android.systemui.volume.panel.volumePanelViewModel
+import com.android.systemui.volume.panel.ui.viewmodel.volumePanelViewModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt
index 64c9429..46df0c2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt
@@ -16,17 +16,16 @@
 
 package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
 
-import android.os.Handler
 import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testCase
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.android.systemui.volume.localMediaController
 import com.android.systemui.volume.mediaControllerRepository
+import com.android.systemui.volume.mediaDeviceSessionInteractor
 import com.android.systemui.volume.mediaOutputInteractor
 import com.android.systemui.volume.panel.shared.model.filterData
 import com.android.systemui.volume.remoteMediaController
@@ -55,12 +54,7 @@
                 listOf(localMediaController, remoteMediaController)
             )
 
-            underTest =
-                MediaDeviceSessionInteractor(
-                    testScope.testScheduler,
-                    Handler(TestableLooper.get(kosmos.testCase).looper),
-                    mediaControllerRepository,
-                )
+            underTest = mediaDeviceSessionInteractor
         }
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt
index 737b7f3..777240c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt
@@ -19,13 +19,13 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.spatializerInteractor
-import com.android.systemui.volume.mediaOutputInteractor
+import com.android.systemui.volume.domain.interactor.audioOutputInteractor
 import com.android.systemui.volume.panel.component.spatial.domain.interactor.SpatialAudioComponentInteractor
 
 val Kosmos.spatialAudioComponentInteractor by
     Kosmos.Fixture {
         SpatialAudioComponentInteractor(
-            mediaOutputInteractor,
+            audioOutputInteractor,
             spatializerInteractor,
             testScope.backgroundScope
         )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
index e36ae60..c6c46fa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
@@ -29,7 +29,6 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.spatializerInteractor
 import com.android.systemui.media.spatializerRepository
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
@@ -37,9 +36,9 @@
 import com.android.systemui.volume.localMediaController
 import com.android.systemui.volume.localMediaRepository
 import com.android.systemui.volume.mediaControllerRepository
-import com.android.systemui.volume.mediaOutputInteractor
 import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
 import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
+import com.android.systemui.volume.panel.component.spatial.spatialAudioComponentInteractor
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
@@ -76,12 +75,7 @@
 
             mediaControllerRepository.setActiveSessions(listOf(localMediaController))
 
-            underTest =
-                SpatialAudioComponentInteractor(
-                    mediaOutputInteractor,
-                    spatializerInteractor,
-                    testScope.backgroundScope,
-                )
+            underTest = spatialAudioComponentInteractor
         }
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt
index 6256eec..ab184ab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt
@@ -22,14 +22,14 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.volume.panel.availableCriteria
-import com.android.systemui.volume.panel.criteriaByKey
-import com.android.systemui.volume.panel.defaultCriteria
+import com.android.systemui.volume.panel.domain.availableCriteria
+import com.android.systemui.volume.panel.domain.defaultCriteria
 import com.android.systemui.volume.panel.domain.model.ComponentModel
-import com.android.systemui.volume.panel.enabledComponents
+import com.android.systemui.volume.panel.domain.unavailableCriteria
 import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
-import com.android.systemui.volume.panel.unavailableCriteria
+import com.android.systemui.volume.panel.ui.composable.enabledComponents
 import com.google.common.truth.Truth.assertThat
+import javax.inject.Provider
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -47,7 +47,7 @@
             with(kosmos) {
                 ComponentsInteractorImpl(
                     enabledComponents,
-                    defaultCriteria,
+                    { defaultCriteria },
                     testScope.backgroundScope,
                     criteriaByKey,
                 )
@@ -66,9 +66,9 @@
                     )
                 criteriaByKey =
                     mapOf(
-                        BOTTOM_BAR to availableCriteria,
-                        COMPONENT_1 to unavailableCriteria,
-                        COMPONENT_2 to availableCriteria,
+                        BOTTOM_BAR to Provider { availableCriteria },
+                        COMPONENT_1 to Provider { unavailableCriteria },
+                        COMPONENT_2 to Provider { availableCriteria },
                     )
                 initUnderTest()
 
@@ -96,8 +96,8 @@
                     )
                 criteriaByKey =
                     mapOf(
-                        BOTTOM_BAR to availableCriteria,
-                        COMPONENT_2 to availableCriteria,
+                        BOTTOM_BAR to Provider { availableCriteria },
+                        COMPONENT_2 to Provider { availableCriteria },
                     )
                 defaultCriteria = unavailableCriteria
                 initUnderTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt
index 3dbf23e..e3dc552 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt
@@ -20,9 +20,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.testKosmos
-import com.android.systemui.volume.panel.componentByKey
-import com.android.systemui.volume.panel.mockVolumePanelUiComponentProvider
 import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.mockVolumePanelUiComponentProvider
 import com.google.common.truth.Truth
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
index 82ce6d7..b37184d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
@@ -20,8 +20,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.testKosmos
-import com.android.systemui.volume.panel.mockVolumePanelUiComponent
 import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.mockVolumePanelUiComponent
 import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
 import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager
 import com.google.common.truth.Truth
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
index 4e06855..f6ada4c16 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
@@ -28,15 +28,15 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.policy.fakeConfigurationController
 import com.android.systemui.testKosmos
-import com.android.systemui.volume.panel.componentByKey
-import com.android.systemui.volume.panel.componentsLayoutManager
-import com.android.systemui.volume.panel.criteriaByKey
-import com.android.systemui.volume.panel.mockVolumePanelUiComponentProvider
+import com.android.systemui.volume.panel.domain.interactor.criteriaByKey
+import com.android.systemui.volume.panel.domain.unavailableCriteria
 import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.mockVolumePanelUiComponentProvider
+import com.android.systemui.volume.panel.ui.composable.componentByKey
 import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager
-import com.android.systemui.volume.panel.unavailableCriteria
-import com.android.systemui.volume.panel.volumePanelViewModel
+import com.android.systemui.volume.panel.ui.layout.componentsLayoutManager
 import com.google.common.truth.Truth.assertThat
+import javax.inject.Provider
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -95,7 +95,7 @@
                     COMPONENT_2 to mockVolumePanelUiComponentProvider,
                     BOTTOM_BAR to mockVolumePanelUiComponentProvider,
                 )
-            criteriaByKey = mapOf(COMPONENT_2 to unavailableCriteria)
+            criteriaByKey = mapOf(COMPONENT_2 to Provider { unavailableCriteria })
         }) {
             testScope.runTest {
                 val componentsLayout by collectLastValue(underTest.componentsLayout)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 79bf5f1..629c96c 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -109,6 +109,12 @@
     val largeClockMessageBuffer: MessageBuffer,
 )
 
+data class AodClockBurnInModel(
+    val scale: Float,
+    val translationX: Float,
+    val translationY: Float,
+)
+
 /** Specifies layout information for the */
 interface ClockFaceLayout {
     /** All clock views to add to the root constraint layout before applying constraints. */
@@ -118,6 +124,8 @@
     fun applyConstraints(constraints: ConstraintSet): ConstraintSet
 
     fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet
+
+    fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel)
 }
 
 /** A ClockFaceLayout that applies the default lockscreen layout to a single view */
@@ -137,6 +145,10 @@
     override fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet {
         return constraints
     }
+
+    override fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel) {
+        // Default clock doesn't need detailed control of view
+    }
 }
 
 /** Events that should call when various rendering parameters change */
diff --git a/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml b/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml
index a751f58..370677ac 100644
--- a/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml
+++ b/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml
@@ -16,5 +16,5 @@
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
     <solid android:color="@color/material_dynamic_neutral20" />
-    <corners android:radius="@dimen/ongoing_call_chip_corner_radius" />
+    <corners android:radius="@dimen/ongoing_activity_chip_corner_radius" />
 </shape>
diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml
index 427373d..b419880 100644
--- a/packages/SystemUI/res-keyguard/values-ar/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml
@@ -23,9 +23,9 @@
     <string name="keyguard_enter_your_pin" msgid="5429932527814874032">"‏أدخل رقم التعريف الشخصي (PIN)"</string>
     <string name="keyguard_enter_pin" msgid="8114529922480276834">"أدخِل رقم التعريف الشخصي"</string>
     <string name="keyguard_enter_your_pattern" msgid="351503370332324745">"أدخل النقش"</string>
-    <string name="keyguard_enter_pattern" msgid="7616595160901084119">"ارسم النقش."</string>
+    <string name="keyguard_enter_pattern" msgid="7616595160901084119">"ارسم النقش"</string>
     <string name="keyguard_enter_your_password" msgid="7225626204122735501">"أدخل كلمة المرور"</string>
-    <string name="keyguard_enter_password" msgid="6483623792371009758">"أدخِل كلمة المرور."</string>
+    <string name="keyguard_enter_password" msgid="6483623792371009758">"أدخِل كلمة المرور"</string>
     <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"بطاقة غير صالحة."</string>
     <string name="keyguard_charged" msgid="5478247181205188995">"اكتمل الشحن"</string>
     <string name="keyguard_plugged_in_wireless" msgid="2537874724955057383">"<xliff:g id="PERCENTAGE">%s</xliff:g> • جارٍ الشحن لاسلكيًا"</string>
@@ -57,11 +57,11 @@
     <string name="kg_wrong_pin" msgid="4160978845968732624">"رقم تعريف شخصي خاطئ"</string>
     <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"لقد أدخلت رقم تعريف شخصي غير صحيح. يُرجى إعادة المحاولة."</string>
     <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"جرّب فتح القفل باستخدام بصمة الإصبع."</string>
-    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"لم يتم التعرّف على البصمة."</string>
+    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"لم يتم التعرّف على البصمة"</string>
     <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"لم يتم التعرّف على الوجه."</string>
-    <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"يُرجى إعادة المحاولة أو إدخال رقم التعريف الشخصي."</string>
-    <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"يُرجى إعادة المحاولة أو إدخال كلمة المرور."</string>
-    <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"يُرجى إعادة المحاولة أو رسم النقش."</string>
+    <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"يُرجى إعادة المحاولة أو إدخال رقم التعريف الشخصي"</string>
+    <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"يُرجى إعادة المحاولة أو إدخال كلمة المرور"</string>
+    <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"يُرجى إعادة المحاولة أو رسم النقش"</string>
     <string name="kg_bio_too_many_attempts_pin" msgid="5850845723433047605">"‏يجب إدخال رقم PIN لأنّك أجريت محاولات كثيرة جدًا."</string>
     <string name="kg_bio_too_many_attempts_password" msgid="5551690347827728042">"يجب إدخال كلمة المرور لأنك أجريت محاولات كثيرة جدًا."</string>
     <string name="kg_bio_too_many_attempts_pattern" msgid="736884689355181602">"يجب رسم النقش لأنّك أجريت محاولات كثيرة جدًا."</string>
diff --git a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
index ee27e83..74b1fd3 100644
--- a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
@@ -57,7 +57,7 @@
     <string name="kg_wrong_pin" msgid="4160978845968732624">"Pogrešan PIN"</string>
     <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"Pogrešan PIN. Probajte ponovo."</string>
     <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"Ili otključajte otiskom prsta"</string>
-    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Otisak prsta neprepoznat"</string>
+    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Otisak prsta nije prepoznat"</string>
     <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"Lice nije prepoznato"</string>
     <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"Probajte ponovo ili unesite PIN"</string>
     <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"Probajte ponovo ili unesite lozinku"</string>
diff --git a/packages/SystemUI/res-keyguard/values-fr/strings.xml b/packages/SystemUI/res-keyguard/values-fr/strings.xml
index 188a76a..822e16e 100644
--- a/packages/SystemUI/res-keyguard/values-fr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr/strings.xml
@@ -23,7 +23,7 @@
     <string name="keyguard_enter_your_pin" msgid="5429932527814874032">"Saisissez votre code"</string>
     <string name="keyguard_enter_pin" msgid="8114529922480276834">"Saisissez le code"</string>
     <string name="keyguard_enter_your_pattern" msgid="351503370332324745">"Tracez votre schéma"</string>
-    <string name="keyguard_enter_pattern" msgid="7616595160901084119">"Dessinez un schéma"</string>
+    <string name="keyguard_enter_pattern" msgid="7616595160901084119">"Dessinez le schéma"</string>
     <string name="keyguard_enter_your_password" msgid="7225626204122735501">"Saisissez votre mot de passe"</string>
     <string name="keyguard_enter_password" msgid="6483623792371009758">"Saisissez le mot de passe"</string>
     <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"Carte non valide."</string>
diff --git a/packages/SystemUI/res-keyguard/values-gl/strings.xml b/packages/SystemUI/res-keyguard/values-gl/strings.xml
index e5cd788..26a4dc5 100644
--- a/packages/SystemUI/res-keyguard/values-gl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gl/strings.xml
@@ -57,11 +57,11 @@
     <string name="kg_wrong_pin" msgid="4160978845968732624">"PIN incorrecto"</string>
     <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"PIN incorrecto. Téntao."</string>
     <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"Tamén podes desbloquealo coa impresión dixital"</string>
-    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Impr. dixital non recoñec."</string>
+    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Impresión dixital non recoñecida"</string>
     <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"A cara non se recoñeceu"</string>
     <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"Téntao de novo ou pon o PIN"</string>
     <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"Téntao de novo ou pon o contrasinal"</string>
-    <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"Téntao de novo ou debuxa o contrasinal"</string>
+    <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"Téntao de novo ou debuxa o padrón"</string>
     <string name="kg_bio_too_many_attempts_pin" msgid="5850845723433047605">"Requírese o PIN tras realizar demasiados intentos"</string>
     <string name="kg_bio_too_many_attempts_password" msgid="5551690347827728042">"Requírese o contrasinal tras demasiados intentos"</string>
     <string name="kg_bio_too_many_attempts_pattern" msgid="736884689355181602">"Requírese o padrón tras realizar demasiados intentos"</string>
diff --git a/packages/SystemUI/res-keyguard/values-hi/strings.xml b/packages/SystemUI/res-keyguard/values-hi/strings.xml
index 2b01903..d905581 100644
--- a/packages/SystemUI/res-keyguard/values-hi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hi/strings.xml
@@ -57,11 +57,11 @@
     <string name="kg_wrong_pin" msgid="4160978845968732624">"गलत पिन"</string>
     <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"गलत पिन. दोबारा डालें."</string>
     <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"फ़िंगरप्रिंट से अनलॉक करें"</string>
-    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"फ़िंगरप्रिंट गलत है"</string>
+    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"फ़िंगरप्रिंट की पहचान नहीं हो पाई"</string>
     <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"चेहरा नहीं पहचाना गया"</string>
     <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"फिर से कोशिश करें या पिन डालें"</string>
     <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"फिर से कोशिश करें या पासवर्ड डालें"</string>
-    <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"फिर से कोशिश करें या पैटर्न ड्रा करें"</string>
+    <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"फिर से कोशिश करें या पैटर्न ड्रॉ करें"</string>
     <string name="kg_bio_too_many_attempts_pin" msgid="5850845723433047605">"कई बार कोशिश की जा चुकी है, इसलिए पिन डालें"</string>
     <string name="kg_bio_too_many_attempts_password" msgid="5551690347827728042">"कई बार कोशिश की जा चुकी है, इसलिए पासवर्ड डालें"</string>
     <string name="kg_bio_too_many_attempts_pattern" msgid="736884689355181602">"कई बार कोशिश की जा चुकी है, इसलिए पैटर्न ड्रा करें"</string>
diff --git a/packages/SystemUI/res-keyguard/values-hr/strings.xml b/packages/SystemUI/res-keyguard/values-hr/strings.xml
index b4b2a19..efc750b 100644
--- a/packages/SystemUI/res-keyguard/values-hr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hr/strings.xml
@@ -61,7 +61,7 @@
     <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"Lice nije prepoznato"</string>
     <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"Pokušajte ponovno ili unesite PIN"</string>
     <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"Pokušajte ponovno ili unesite zaporku"</string>
-    <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"Pokušajte ponovno ili izradite uzorak"</string>
+    <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"Pokušajte ponovno ili nacrtajte uzorak"</string>
     <string name="kg_bio_too_many_attempts_pin" msgid="5850845723433047605">"PIN je obavezan nakon previše pokušaja"</string>
     <string name="kg_bio_too_many_attempts_password" msgid="5551690347827728042">"Zaporka je obavezna nakon previše pokušaja"</string>
     <string name="kg_bio_too_many_attempts_pattern" msgid="736884689355181602">"Uzorak je obavezan nakon previše pokušaja"</string>
diff --git a/packages/SystemUI/res-keyguard/values-my/strings.xml b/packages/SystemUI/res-keyguard/values-my/strings.xml
index c4fc5f7..2bace4a 100644
--- a/packages/SystemUI/res-keyguard/values-my/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-my/strings.xml
@@ -116,7 +116,7 @@
     <string name="kg_prompt_added_security_password" msgid="6053156069765029006">"ထပ်ဆောင်း လုံခြုံရေးအတွက် စကားဝှက် လိုအပ်သည်"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"စက်ပစ္စည်းကို စီမံခန့်ခွဲသူက လော့ခ်ချထားပါသည်"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"စက်ပစ္စည်းကို ကိုယ်တိုင်ကိုယ်ကျ လော့ခ်ချထားခဲ့သည်"</string>
-    <string name="kg_face_not_recognized" msgid="7903950626744419160">"မသိ"</string>
+    <string name="kg_face_not_recognized" msgid="7903950626744419160">"မသိပါ"</string>
     <string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"‘မျက်နှာပြ လော့ခ်ဖွင့်ခြင်း’ သုံးရန် ‘ဆက်တင်များ’ တွင်ကင်မရာသုံးခွင့်ကိုဖွင့်ပါ"</string>
     <string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{ဆင်းမ်ပင်နံပါတ် ထည့်သွင်းပါ။ သင့်စက်ကို လော့ခ်ဖွင့်ရန် မိုဘိုင်းဖုန်းကုမ္ပဏီသို့ မဆက်သွယ်မီ # ကြိမ် ကြိုးစားခွင့်ရှိသေးသည်။}other{ဆင်းမ်ပင်နံပါတ် ထည့်သွင်းပါ။ သင့်တွင် # ကြိမ် ကြိုးစားခွင့်ရှိသေးသည်။}}"</string>
     <string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{ဆင်းမ်ကတ်သည် ယခု ပိတ်သွားပါပြီ။ ရှေ့ဆက်ရန် PUK ကုဒ်ကို ထည့်ပါ။ ဆင်းမ်ကတ် အပြီးပိတ်မသွားမီ သင့်တွင် # ကြိမ် ကြိုးစားခွင့်ရှိသေးသည်။ အသေးစိတ်အတွက် မိုဘိုင်းဖုန်းကုမ္ပဏီကို ဆက်သွယ်ပါ။}other{ဆင်းမ်ကတ်သည် ယခု ပိတ်သွားပါပြီ။ ရှေ့ဆက်ရန် PUK ကုဒ်ကို ထည့်ပါ။ ဆင်းမ်ကတ် အပြီးပိတ်မသွားမီ သင့်တွင် # ကြိမ် ကြိုးစားခွင့်ရှိသေးသည်။ အသေးစိတ်အတွက် မိုဘိုင်းဖုန်းကုမ္ပဏီကို ဆက်သွယ်ပါ။}}"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ne/strings.xml b/packages/SystemUI/res-keyguard/values-ne/strings.xml
index 3dd4243..224f1ae 100644
--- a/packages/SystemUI/res-keyguard/values-ne/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ne/strings.xml
@@ -57,8 +57,8 @@
     <string name="kg_wrong_pin" msgid="4160978845968732624">"गलत PIN"</string>
     <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"PIN मिलेन। फेरि प्रयास गर्नुहोस्।"</string>
     <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"वा फिंगरप्रिन्ट प्रयोग गरी अनलक गर्नुहोस्"</string>
-    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"फिंगरप्रिन्ट पहिचान गर्न सकिएन"</string>
-    <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"अनुहार पहिचान गर्न सकिएन"</string>
+    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"फिंगरप्रिन्ट मिलेन"</string>
+    <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"अनुहार मिलेन"</string>
     <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"फेरि प्रयास गर्नुहोस् वा PIN हाल्नुहोस्"</string>
     <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"फेरि प्रयास गर्नुहोस् वा पासवर्ड हाल्नुहोस्"</string>
     <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"फेरि प्रयास गर्नुहोस् वा प्याटर्न हाल्नुहोस्"</string>
diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml
index c3863a9..a35c3e6 100644
--- a/packages/SystemUI/res-keyguard/values-nl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml
@@ -59,7 +59,7 @@
     <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"Of ontgrendel met vingerafdruk"</string>
     <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Vingerafdruk niet herkend"</string>
     <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"Gezicht niet herkend"</string>
-    <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"Probeer het opnieuw of geef de pincode op"</string>
+    <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"Probeer het opnieuw of voer de pincode in"</string>
     <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"Probeer het opnieuw of geef het wachtwoord op"</string>
     <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"Probeer het opnieuw of teken het patroon"</string>
     <string name="kg_bio_too_many_attempts_pin" msgid="5850845723433047605">"Na te veel pogingen is de pincode vereist"</string>
diff --git a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
index b292204..9a26f7d 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
@@ -57,7 +57,7 @@
     <string name="kg_wrong_pin" msgid="4160978845968732624">"PIN incorreto"</string>
     <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"PIN errado. Tente de novo."</string>
     <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"Ou desbloqueie com a impressão digital"</string>
-    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Impr. dig. não reconhecida"</string>
+    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Impressão digital não reconhecida"</string>
     <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"Rosto não reconhecido"</string>
     <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"Tente novamente ou introduza o PIN"</string>
     <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"Tente novamente ou introduza a palavra-passe"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sk/strings.xml b/packages/SystemUI/res-keyguard/values-sk/strings.xml
index 9b0647a..4dffd97 100644
--- a/packages/SystemUI/res-keyguard/values-sk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sk/strings.xml
@@ -57,7 +57,7 @@
     <string name="kg_wrong_pin" msgid="4160978845968732624">"Nesprávny kód PIN"</string>
     <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"Nesprávny kód PIN. Zopakujte."</string>
     <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"Alebo odomknite odtlačkom prsta"</string>
-    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Nerozpoz. odtlačok prsta"</string>
+    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Odtlačok prsta nebol rozpoznaný"</string>
     <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"Tvár nebola rozpoznaná"</string>
     <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"Skúste to znova alebo zadajte PIN"</string>
     <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"Skúste to znova alebo zadajte heslo"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sr/strings.xml b/packages/SystemUI/res-keyguard/values-sr/strings.xml
index 34c9311..339a82a 100644
--- a/packages/SystemUI/res-keyguard/values-sr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sr/strings.xml
@@ -57,7 +57,7 @@
     <string name="kg_wrong_pin" msgid="4160978845968732624">"Погрешан PIN"</string>
     <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"Погрешан PIN. Пробајте поново."</string>
     <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"Или откључајте отиском прста"</string>
-    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Отисак прста непрепознат"</string>
+    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Отисак прста није препознат"</string>
     <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"Лице није препознато"</string>
     <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"Пробајте поново или унесите PIN"</string>
     <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"Пробајте поново или унесите лозинку"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ta/strings.xml b/packages/SystemUI/res-keyguard/values-ta/strings.xml
index 60f60c4..b73372f7 100644
--- a/packages/SystemUI/res-keyguard/values-ta/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ta/strings.xml
@@ -57,7 +57,7 @@
     <string name="kg_wrong_pin" msgid="4160978845968732624">"தவறான பின்"</string>
     <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"தவறு. மீண்டும் முயலவும்."</string>
     <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"இல்லையெனில் கைரேகை மூலம் அன்லாக் செய்யவும்"</string>
-    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"கைரேகை அடையாளம் இல்லை"</string>
+    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"கைரேகையை அடையாளம் காண முடியவில்லை"</string>
     <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"முகம் கண்டறிய முடியவில்லை"</string>
     <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"மீண்டும் முயலவும் அல்லது பின்னை உள்ளிடவும்"</string>
     <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"மீண்டும் முயலவும் அல்லது கடவுச்சொல்லை உள்ளிடவும்"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ur/strings.xml b/packages/SystemUI/res-keyguard/values-ur/strings.xml
index fa2a234..fcb3a3e 100644
--- a/packages/SystemUI/res-keyguard/values-ur/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ur/strings.xml
@@ -116,7 +116,7 @@
     <string name="kg_prompt_added_security_password" msgid="6053156069765029006">"اضافی سیکیورٹی کیلئے پاس ورڈ درکار ہے"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"آلہ منتظم کی جانب سے مقفل ہے"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"آلہ کو دستی طور پر مقفل کیا گیا تھا"</string>
-    <string name="kg_face_not_recognized" msgid="7903950626744419160">"تسلیم شدہ نہیں ہے"</string>
+    <string name="kg_face_not_recognized" msgid="7903950626744419160">"شناخت نہیں ہو سکی"</string>
     <string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"فیس اَنلاک استعمال کرنے کیلئے، ترتیبات میں کیمرا تک رسائی کو آن کریں"</string>
     <string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{‏SIM کا PIN درج کریں۔ اس سے پہلے کہ آپ اپنا آلہ غیر مقفل کرنے کیلئے لازمی طور پر اپنے کیریئر سے رابطہ کریں آپ کے پاس # کوشش بچی ہے۔}other{‏SIM کا PIN درج کریں۔ آپ کے پاس # کوششیں بچی ہیں۔}}"</string>
     <string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{‏SIM اب غیر فعال ہے۔ جاری رکھنے کیلئے PUK کوڈ درج کریں۔ SIM کے مستقل طور پر ناقابل استعمال ہونے سے پہلے آپ کے پاس # کوشش بچی ہے۔ تفصیلات کیلئے کیریئر سے رابطہ کریں۔}other{‏SIM اب غیر فعال ہے۔ جاری رکھنے کیلئے PUK کوڈ درج کریں۔ SIM کے مستقل طور پر ناقابل استعمال ہونے سے پہلے آپ کے پاس # کوششیں بچی ہیں۔ تفصیلات کیلئے کیریئر سے رابطہ کریں۔}}"</string>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
index 7d74c9c..196df28 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
@@ -23,7 +23,7 @@
     <string name="keyguard_enter_your_pin" msgid="5429932527814874032">"输入您的 PIN 码"</string>
     <string name="keyguard_enter_pin" msgid="8114529922480276834">"输入 PIN 码"</string>
     <string name="keyguard_enter_your_pattern" msgid="351503370332324745">"绘制解锁图案"</string>
-    <string name="keyguard_enter_pattern" msgid="7616595160901084119">"绘制图案"</string>
+    <string name="keyguard_enter_pattern" msgid="7616595160901084119">"绘制解锁图案"</string>
     <string name="keyguard_enter_your_password" msgid="7225626204122735501">"输入您的密码"</string>
     <string name="keyguard_enter_password" msgid="6483623792371009758">"输入密码"</string>
     <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"SIM 卡无效。"</string>
@@ -61,7 +61,7 @@
     <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"无法识别面孔"</string>
     <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"请重试,或输入 PIN 码"</string>
     <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"请重试,或输入密码"</string>
-    <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"请重试,或绘制图案"</string>
+    <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"请重试,或绘制解锁图案"</string>
     <string name="kg_bio_too_many_attempts_pin" msgid="5850845723433047605">"如果出错的尝试次数太多,必须输入 PIN 码才能解锁"</string>
     <string name="kg_bio_too_many_attempts_password" msgid="5551690347827728042">"如果出错的尝试次数太多,必须输入密码才能解锁"</string>
     <string name="kg_bio_too_many_attempts_pattern" msgid="736884689355181602">"如果出错的尝试次数太多,必须绘制图案才能解锁"</string>
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index c43e394..da12dd7 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -36,7 +36,6 @@
     </style>
     <style name="Keyguard.Bouncer.SecondaryMessage" parent="Theme.SystemUI">
         <item name="android:textSize">14sp</item>
-        <item name="android:lineHeight">20dp</item>
         <item name="android:maxLines">@integer/bouncer_secondary_message_lines</item>
         <item name="android:lines">@integer/bouncer_secondary_message_lines</item>
         <item name="android:textAlignment">center</item>
diff --git a/packages/SystemUI/res/drawable/contrast_dialog_button_background.xml b/packages/SystemUI/res/drawable/contrast_dialog_button_background.xml
deleted file mode 100644
index 4181220..0000000
--- a/packages/SystemUI/res/drawable/contrast_dialog_button_background.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-* Copyright 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.
-*/
--->
-<selector
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-
-    <item android:state_selected="true">
-        <shape android:shape="rectangle">
-            <solid android:color="?androidprv:attr/colorSurfaceHighlight" />
-            <stroke
-                android:color="?androidprv:attr/colorAccentPrimary"
-                android:width="@dimen/contrast_dialog_button_stroke_width" />
-            <corners android:radius="@dimen/contrast_dialog_button_radius"/>
-        </shape>
-    </item>
-
-    <item>
-        <layer-list>
-            <item android:top="@dimen/contrast_dialog_button_stroke_width"
-                android:bottom="@dimen/contrast_dialog_button_stroke_width"
-                android:left="@dimen/contrast_dialog_button_stroke_width"
-                android:right="@dimen/contrast_dialog_button_stroke_width">
-                <shape android:shape="rectangle">
-                    <solid android:color="?androidprv:attr/colorSurfaceHighlight" />
-                    <corners android:radius="@dimen/contrast_dialog_button_radius"/>
-                </shape>
-            </item>
-        </layer-list>
-    </item>
-</selector>
diff --git a/packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml b/packages/SystemUI/res/drawable/hub_handle.xml
similarity index 77%
copy from packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml
copy to packages/SystemUI/res/drawable/hub_handle.xml
index bdd6270..8bc276f 100644
--- a/packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml
+++ b/packages/SystemUI/res/drawable/hub_handle.xml
@@ -1,5 +1,5 @@
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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.
@@ -15,6 +15,6 @@
   -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="?android:attr/colorAccent" />
-    <corners android:radius="@dimen/ongoing_call_chip_corner_radius" />
+    <corners android:radius="4dp" />
+    <solid android:color="#FFFFFF" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_contrast_high.xml b/packages/SystemUI/res/drawable/ic_contrast_high.xml
deleted file mode 100644
index aa5b5ab..0000000
--- a/packages/SystemUI/res/drawable/ic_contrast_high.xml
+++ /dev/null
@@ -1,25 +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.
-  -->
-<vector android:autoMirrored="true" android:height="20dp"
-    android:viewportHeight="20" android:viewportWidth="66"
-    android:width="66dp" xmlns:android="http://schemas.android.com/apk/res/android">
-    <path android:fillColor="#F2F1E8"
-        android:pathData="M0.5,8C0.5,3.858 3.858,0.5 8,0.5H58C62.142,0.5 65.5,3.858 65.5,8V12C65.5,16.142 62.142,19.5 58,19.5H8C3.858,19.5 0.5,16.142 0.5,12V8Z"
-        android:strokeColor="#1B1C17" android:strokeWidth="1"/>
-    <path android:fillColor="#1B1C17" android:pathData="M11,10m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/>
-    <path android:fillColor="#1B1C17" android:pathData="M23,5L43,5A2,2 0,0 1,45 7L45,7A2,2 0,0 1,43 9L23,9A2,2 0,0 1,21 7L21,7A2,2 0,0 1,23 5z"/>
-    <path android:fillColor="#1B1C17" android:pathData="M23,11L55,11A2,2 0,0 1,57 13L57,13A2,2 0,0 1,55 15L23,15A2,2 0,0 1,21 13L21,13A2,2 0,0 1,23 11z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_contrast_medium.xml b/packages/SystemUI/res/drawable/ic_contrast_medium.xml
deleted file mode 100644
index 89519b8..0000000
--- a/packages/SystemUI/res/drawable/ic_contrast_medium.xml
+++ /dev/null
@@ -1,23 +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.
-  -->
-<vector android:autoMirrored="true" android:height="20dp"
-    android:viewportHeight="20" android:viewportWidth="66"
-    android:width="66dp" xmlns:android="http://schemas.android.com/apk/res/android">
-    <path android:fillColor="#F2F1E8" android:pathData="M0,8C0,3.582 3.582,0 8,0H58C62.418,0 66,3.582 66,8V12C66,16.418 62.418,20 58,20H8C3.582,20 0,16.418 0,12V8Z"/>
-    <path android:fillColor="#919283" android:pathData="M11,10m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/>
-    <path android:fillColor="#919283" android:pathData="M23,5L43,5A2,2 0,0 1,45 7L45,7A2,2 0,0 1,43 9L23,9A2,2 0,0 1,21 7L21,7A2,2 0,0 1,23 5z"/>
-    <path android:fillColor="#919283" android:pathData="M23,11L55,11A2,2 0,0 1,57 13L57,13A2,2 0,0 1,55 15L23,15A2,2 0,0 1,21 13L21,13A2,2 0,0 1,23 11z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_contrast_standard.xml b/packages/SystemUI/res/drawable/ic_contrast_standard.xml
deleted file mode 100644
index f914975..0000000
--- a/packages/SystemUI/res/drawable/ic_contrast_standard.xml
+++ /dev/null
@@ -1,23 +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.
-  -->
-<vector android:autoMirrored="true" android:height="20dp"
-    android:viewportHeight="20" android:viewportWidth="66"
-    android:width="66dp" xmlns:android="http://schemas.android.com/apk/res/android">
-    <path android:fillColor="#C7C8B7" android:pathData="M0,8C0,3.582 3.582,0 8,0H58C62.418,0 66,3.582 66,8V12C66,16.418 62.418,20 58,20H8C3.582,20 0,16.418 0,12V8Z"/>
-    <path android:fillColor="#919283" android:pathData="M11,10m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/>
-    <path android:fillColor="#919283" android:pathData="M23,5L43,5A2,2 0,0 1,45 7L45,7A2,2 0,0 1,43 9L23,9A2,2 0,0 1,21 7L21,7A2,2 0,0 1,23 5z"/>
-    <path android:fillColor="#919283" android:pathData="M23,11L55,11A2,2 0,0 1,57 13L57,13A2,2 0,0 1,55 15L23,15A2,2 0,0 1,21 13L21,13A2,2 0,0 1,23 11z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml b/packages/SystemUI/res/drawable/ongoing_activity_chip_bg.xml
similarity index 90%
rename from packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml
rename to packages/SystemUI/res/drawable/ongoing_activity_chip_bg.xml
index bdd6270..b9a4cbf 100644
--- a/packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml
+++ b/packages/SystemUI/res/drawable/ongoing_activity_chip_bg.xml
@@ -16,5 +16,5 @@
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
     <solid android:color="?android:attr/colorAccent" />
-    <corners android:radius="@dimen/ongoing_call_chip_corner_radius" />
+    <corners android:radius="@dimen/ongoing_activity_chip_corner_radius" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml b/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml
index cf7a730..a30a122 100644
--- a/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml
+++ b/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml
@@ -13,12 +13,16 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
-    <!-- Since this layer list has just one layer, we can remove it and replace with the inner
-         layer drawable. However this should only be done when the flag
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/qs_tile_ripple_color">
+    <!-- We don't really use the ripple effect here, but changing it to LayerDrawable causes
+         performance regression, see: b/339412453.
+         Since this ripple has just one layer inside, we can try to remove that extra "background"
+         layer. However this should only be done when the flag
          com.android.systemui.qs_tile_focus_state has completed all its stages and this drawable
          fully replaces the previous one to ensure consistency with code sections searching for
-         specific ids in drawable hierarchy -->
+         specific ids in drawable hierarchy 
+         -->
     <item
         android:id="@id/background">
         <layer-list>
@@ -48,4 +52,4 @@
             </item>
         </layer-list>
     </item>
-</layer-list>
\ No newline at end of file
+</ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/shelf_action_chip_divider.xml b/packages/SystemUI/res/drawable/shelf_action_chip_divider.xml
index a5b44e5..0a1f2a8 100644
--- a/packages/SystemUI/res/drawable/shelf_action_chip_divider.xml
+++ b/packages/SystemUI/res/drawable/shelf_action_chip_divider.xml
@@ -16,6 +16,6 @@
 
 <shape xmlns:android = "http://schemas.android.com/apk/res/android">
     <size
-        android:width = "@dimen/overlay_action_chip_margin_start"
+        android:width = "@dimen/shelf_action_chip_margin_start"
         android:height = "0dp"/>
 </shape>
diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
index f644584f..01b9f7e 100644
--- a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
@@ -26,8 +26,8 @@
         android:paddingVertical="16dp"
         android:visibility="visible"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
-        app:layout_constraintStart_toStartOf="@+id/leftGuideline"
+        app:layout_constraintRight_toLeftOf="@+id/rightGuideline"
+        app:layout_constraintLeft_toLeftOf="@+id/leftGuideline"
         app:layout_constraintTop_toTopOf="@+id/topGuideline" />
 
     <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
@@ -35,8 +35,8 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
         app:layout_constraintTop_toTopOf="parent"
         tools:srcCompat="@tools:sample/avatars" />
 
@@ -47,6 +47,7 @@
         android:layout_gravity="center"
         android:contentDescription="@null"
         android:scaleType="fitXY"
+        android:importantForAccessibility="no"
         app:layout_constraintBottom_toBottomOf="@+id/biometric_icon"
         app:layout_constraintEnd_toEndOf="@+id/biometric_icon"
         app:layout_constraintStart_toStartOf="@+id/biometric_icon"
@@ -62,8 +63,8 @@
         android:paddingTop="24dp"
         android:fadeScrollbars="false"
         app:layout_constraintBottom_toTopOf="@+id/button_bar"
-        app:layout_constraintEnd_toStartOf="@+id/midGuideline"
-        app:layout_constraintStart_toStartOf="@id/leftGuideline"
+        app:layout_constraintRight_toLeftOf="@+id/midGuideline"
+        app:layout_constraintLeft_toLeftOf="@id/leftGuideline"
         app:layout_constraintTop_toTopOf="@+id/topGuideline">
 
         <androidx.constraintlayout.widget.ConstraintLayout
@@ -88,7 +89,7 @@
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
                 android:textAlignment="viewStart"
-                android:paddingLeft="16dp"
+                android:paddingStart="16dp"
                 app:layout_constraintBottom_toBottomOf="@+id/logo"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toEndOf="@+id/logo"
@@ -208,6 +209,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="vertical"
+        app:guidelineUseRtl="false"
         app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" />
 
     <androidx.constraintlayout.widget.Guideline
@@ -215,6 +217,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="vertical"
+        app:guidelineUseRtl="false"
         app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
 
     <androidx.constraintlayout.widget.Guideline
@@ -222,6 +225,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="vertical"
+        app:guidelineUseRtl="false"
         app:layout_constraintGuide_begin="406dp" />
 
     <androidx.constraintlayout.widget.Guideline
diff --git a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
index 46b8e46..05f6fae 100644
--- a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
@@ -229,6 +229,7 @@
         android:layout_gravity="center"
         android:contentDescription="@null"
         android:scaleType="fitXY"
+        android:importantForAccessibility="no"
         app:layout_constraintBottom_toBottomOf="@+id/biometric_icon"
         app:layout_constraintEnd_toEndOf="@+id/biometric_icon"
         app:layout_constraintStart_toStartOf="@+id/biometric_icon"
diff --git a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
index 4d2310a..0bbe73c 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
@@ -27,7 +27,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
-        android:layout_marginLeft="24dp"
+        android:layout_marginStart="24dp"
         android:ellipsize="end"
         android:maxLines="2"
         android:visibility="invisible"
@@ -41,7 +41,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
-        android:layout_marginLeft="24dp"
+        android:layout_marginStart="24dp"
         android:text="@string/cancel"
         android:visibility="invisible"
         app:layout_constraintBottom_toBottomOf="parent"
@@ -54,7 +54,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
-        android:layout_marginLeft="24dp"
+        android:layout_marginStart="24dp"
         android:visibility="invisible"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintStart_toStartOf="parent" />
@@ -66,7 +66,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
-        android:layout_marginRight="24dp"
+        android:layout_marginEnd="24dp"
         android:ellipsize="end"
         android:maxLines="2"
         android:text="@string/biometric_dialog_confirm"
@@ -81,7 +81,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
-        android:layout_marginRight="24dp"
+        android:layout_marginEnd="24dp"
         android:ellipsize="end"
         android:maxLines="2"
         android:text="@string/biometric_dialog_try_again"
diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
index d51fe58..fa4d9a8 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
@@ -222,6 +222,7 @@
         android:layout_gravity="center"
         android:contentDescription="@null"
         android:scaleType="fitXY"
+        android:importantForAccessibility="no"
         app:layout_constraintBottom_toBottomOf="@+id/biometric_icon"
         app:layout_constraintEnd_toEndOf="@+id/biometric_icon"
         app:layout_constraintStart_toStartOf="@+id/biometric_icon"
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index 76d10cc..a598007 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -136,7 +136,8 @@
             <TextView
                 android:id="@+id/bluetooth_auto_on_toggle_title"
                 android:layout_width="0dp"
-                android:layout_height="68dp"
+                android:layout_height="wrap_content"
+                android:minHeight="68dp"
                 android:layout_marginBottom="20dp"
                 android:maxLines="2"
                 android:ellipsize="end"
diff --git a/packages/SystemUI/res/layout/clipboard_overlay2.xml b/packages/SystemUI/res/layout/clipboard_overlay2.xml
new file mode 100644
index 0000000..521369e
--- /dev/null
+++ b/packages/SystemUI/res/layout/clipboard_overlay2.xml
@@ -0,0 +1,191 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<com.android.systemui.clipboardoverlay.ClipboardOverlayView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/clipboard_ui"
+    android:theme="@style/FloatingOverlay"
+    android:alpha="0"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:contentDescription="@string/clipboard_overlay_window_name">
+    <FrameLayout
+        android:id="@+id/actions_container_background"
+        android:visibility="gone"
+        android:layout_height="0dp"
+        android:layout_width="0dp"
+        android:elevation="4dp"
+        android:background="@drawable/shelf_action_chip_container_background"
+        android:layout_marginStart="@dimen/overlay_action_container_minimum_edge_spacing"
+        android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="@+id/actions_container"
+        app:layout_constraintEnd_toEndOf="@+id/actions_container"
+        app:layout_constraintBottom_toBottomOf="parent"/>
+    <HorizontalScrollView
+        android:id="@+id/actions_container"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
+        android:paddingEnd="@dimen/overlay_action_container_padding_end"
+        android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
+        android:elevation="4dp"
+        android:scrollbars="none"
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constraintWidth_percent="1.0"
+        app:layout_constraintWidth_max="wrap"
+        app:layout_constraintStart_toEndOf="@+id/preview_border"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintBottom_toBottomOf="@id/actions_container_background">
+        <LinearLayout
+            android:id="@+id/actions"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingStart="@dimen/shelf_action_chip_margin_start"
+            android:showDividers="middle"
+            android:divider="@drawable/shelf_action_chip_divider"
+            android:animateLayoutChanges="true">
+            <include layout="@layout/shelf_action_chip"
+                     android:id="@+id/share_chip"/>
+            <include layout="@layout/shelf_action_chip"
+                     android:id="@+id/remote_copy_chip"/>
+        </LinearLayout>
+    </HorizontalScrollView>
+    <View
+        android:id="@+id/preview_border"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginStart="@dimen/overlay_preview_container_margin"
+        android:layout_marginTop="@dimen/overlay_border_width_neg"
+        android:layout_marginEnd="@dimen/overlay_border_width_neg"
+        android:layout_marginBottom="@dimen/overlay_preview_container_margin"
+        android:elevation="7dp"
+        android:background="@drawable/overlay_border"
+        app:layout_constraintStart_toStartOf="@id/actions_container_background"
+        app:layout_constraintTop_toTopOf="@id/clipboard_preview"
+        app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
+        app:layout_constraintBottom_toBottomOf="@id/actions_container_background"/>
+    <FrameLayout
+        android:id="@+id/clipboard_preview"
+        android:layout_width="@dimen/clipboard_preview_size"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/overlay_border_width"
+        android:layout_marginBottom="@dimen/overlay_border_width"
+        android:layout_gravity="center"
+        android:elevation="7dp"
+        android:background="@drawable/overlay_preview_background"
+        android:clipChildren="true"
+        android:clipToOutline="true"
+        android:clipToPadding="true"
+        app:layout_constraintStart_toStartOf="@id/preview_border"
+        app:layout_constraintBottom_toBottomOf="@id/preview_border">
+        <TextView android:id="@+id/text_preview"
+                  android:textFontWeight="500"
+                  android:padding="8dp"
+                  android:gravity="center|start"
+                  android:ellipsize="end"
+                  android:autoSizeTextType="uniform"
+                  android:autoSizeMinTextSize="@dimen/clipboard_overlay_min_font"
+                  android:autoSizeMaxTextSize="@dimen/clipboard_overlay_max_font"
+                  android:textColor="?attr/overlayButtonTextColor"
+                  android:textColorLink="?attr/overlayButtonTextColor"
+                  android:background="?androidprv:attr/colorAccentSecondary"
+                  android:layout_width="@dimen/clipboard_preview_size"
+                  android:layout_height="@dimen/clipboard_preview_size"/>
+        <ImageView
+            android:id="@+id/image_preview"
+            android:scaleType="fitCenter"
+            android:adjustViewBounds="true"
+            android:contentDescription="@string/clipboard_image_preview"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+        <TextView
+            android:id="@+id/hidden_preview"
+            android:visibility="gone"
+            android:textFontWeight="500"
+            android:padding="8dp"
+            android:gravity="center"
+            android:textSize="14sp"
+            android:textColor="?attr/overlayButtonTextColor"
+            android:background="?androidprv:attr/colorAccentSecondary"
+            android:layout_width="@dimen/clipboard_preview_size"
+            android:layout_height="@dimen/clipboard_preview_size"/>
+    </FrameLayout>
+    <LinearLayout
+        android:id="@+id/minimized_preview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:visibility="gone"
+        android:elevation="7dp"
+        android:padding="8dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+        android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
+        android:background="@drawable/clipboard_minimized_background">
+        <ImageView
+            android:src="@drawable/ic_content_paste"
+            android:tint="?attr/overlayButtonTextColor"
+            android:layout_width="24dp"
+            android:layout_height="24dp"/>
+        <ImageView
+            android:src="@*android:drawable/ic_chevron_end"
+            android:tint="?attr/overlayButtonTextColor"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:paddingEnd="-8dp"
+            android:paddingStart="-4dp"/>
+    </LinearLayout>
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/clipboard_content_top"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:barrierDirection="top"
+        app:constraint_referenced_ids="clipboard_preview,minimized_preview"/>
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/clipboard_content_end"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:barrierDirection="end"
+        app:constraint_referenced_ids="clipboard_preview,minimized_preview"/>
+    <FrameLayout
+        android:id="@+id/dismiss_button"
+        android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
+        android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
+        android:elevation="10dp"
+        android:visibility="gone"
+        android:alpha="0"
+        app:layout_constraintStart_toEndOf="@id/clipboard_content_end"
+        app:layout_constraintEnd_toEndOf="@id/clipboard_content_end"
+        app:layout_constraintTop_toTopOf="@id/clipboard_content_top"
+        app:layout_constraintBottom_toTopOf="@id/clipboard_content_top"
+        android:contentDescription="@string/clipboard_dismiss_description">
+        <ImageView
+            android:id="@+id/dismiss_image"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_margin="@dimen/overlay_dismiss_button_margin"
+            android:background="@drawable/circular_background"
+            android:backgroundTint="?androidprv:attr/materialColorPrimaryFixedDim"
+            android:tint="?androidprv:attr/materialColorOnPrimaryFixed"
+            android:padding="4dp"
+            android:src="@drawable/ic_close"/>
+    </FrameLayout>
+</com.android.systemui.clipboardoverlay.ClipboardOverlayView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/contrast_dialog.xml b/packages/SystemUI/res/layout/contrast_dialog.xml
deleted file mode 100644
index 8e885cf..0000000
--- a/packages/SystemUI/res/layout/contrast_dialog.xml
+++ /dev/null
@@ -1,127 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  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.
-  -->
-
-<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="horizontal">
-
-    <Space
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_weight="1"/>
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="vertical">
-
-        <FrameLayout
-            android:id="@+id/contrast_button_standard"
-            android:layout_width="@dimen/contrast_dialog_button_total_size"
-            android:layout_height="@dimen/contrast_dialog_button_total_size"
-            android:background="@drawable/contrast_dialog_button_background">
-
-            <ImageView
-                android:layout_gravity="center"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:src="@drawable/ic_contrast_standard"/>
-        </FrameLayout>
-
-        <TextView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/contrast_dialog_button_text_spacing"
-            android:gravity="center_horizontal|top"
-            android:textSize="@dimen/contrast_dialog_button_text_size"
-            android:text="@string/quick_settings_contrast_standard"
-            android:textColor="?androidprv:attr/textColorPrimary"/>
-    </LinearLayout>
-
-    <Space
-        android:layout_width="@dimen/contrast_dialog_button_horizontal_spacing"
-        android:layout_height="match_parent" />
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="vertical">
-
-        <FrameLayout
-            android:id="@+id/contrast_button_medium"
-            android:layout_width="@dimen/contrast_dialog_button_total_size"
-            android:layout_height="@dimen/contrast_dialog_button_total_size"
-            android:background="@drawable/contrast_dialog_button_background">
-
-            <ImageView
-                android:layout_gravity="center"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:src="@drawable/ic_contrast_medium"/>
-        </FrameLayout>
-
-        <TextView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/contrast_dialog_button_text_spacing"
-            android:gravity="center_horizontal|top"
-            android:textSize="@dimen/contrast_dialog_button_text_size"
-            android:text="@string/quick_settings_contrast_medium"
-            android:textColor="?androidprv:attr/textColorPrimary"/>
-    </LinearLayout>
-
-    <Space
-        android:layout_width="@dimen/contrast_dialog_button_horizontal_spacing"
-        android:layout_height="match_parent" />
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="vertical">
-
-        <FrameLayout
-            android:id="@+id/contrast_button_high"
-            android:layout_width="@dimen/contrast_dialog_button_total_size"
-            android:layout_height="@dimen/contrast_dialog_button_total_size"
-            android:background="@drawable/contrast_dialog_button_background">
-
-            <ImageView
-                android:layout_gravity="center"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:src="@drawable/ic_contrast_high"/>
-
-        </FrameLayout>
-
-        <TextView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/contrast_dialog_button_text_spacing"
-            android:gravity="center_horizontal|top"
-            android:textSize="@dimen/contrast_dialog_button_text_size"
-            android:text="@string/quick_settings_contrast_high"
-            android:textColor="?androidprv:attr/textColorPrimary"/>
-    </LinearLayout>
-
-    <Space
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_weight="1"/>
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml
index 19fb874..4234fca5 100644
--- a/packages/SystemUI/res/layout/dream_overlay_container.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_container.xml
@@ -21,6 +21,19 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
+    <ImageView
+        android:id="@+id/glanceable_hub_handle"
+        android:layout_width="4dp"
+        android:layout_height="220dp"
+        android:layout_centerVertical="true"
+        android:layout_marginEnd="12dp"
+        android:background="@drawable/hub_handle"
+        android:visibility="gone"
+        android:contentDescription="UI indicator for swiping open the glanceable hub"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
     <androidx.constraintlayout.widget.ConstraintLayout
         android:id="@+id/dream_overlay_content"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/ongoing_call_chip.xml b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
similarity index 66%
rename from packages/SystemUI/res/layout/ongoing_call_chip.xml
rename to packages/SystemUI/res/layout/ongoing_activity_chip.xml
index 6a0217ec..a33be12 100644
--- a/packages/SystemUI/res/layout/ongoing_call_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
@@ -17,43 +17,45 @@
      the chip. -->
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/ongoing_call_chip"
+    android:id="@+id/ongoing_activity_chip"
     android:layout_width="wrap_content"
     android:layout_height="match_parent"
     android:layout_gravity="center_vertical|start"
     android:layout_marginStart="5dp"
 >
-    <com.android.systemui.statusbar.phone.ongoingcall.OngoingCallBackgroundContainer
-        android:id="@+id/ongoing_call_chip_background"
+    <!-- TODO(b/332662551): Update this content description when this supports more than just
+         phone calls. -->
+    <com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+        android:id="@+id/ongoing_activity_chip_background"
         android:layout_width="wrap_content"
         android:layout_height="@dimen/ongoing_appops_chip_height"
         android:layout_gravity="center_vertical"
         android:gravity="center"
-        android:background="@drawable/ongoing_call_chip_bg"
-        android:paddingStart="@dimen/ongoing_call_chip_side_padding"
-        android:paddingEnd="@dimen/ongoing_call_chip_side_padding"
+        android:background="@drawable/ongoing_activity_chip_bg"
+        android:paddingStart="@dimen/ongoing_activity_chip_side_padding"
+        android:paddingEnd="@dimen/ongoing_activity_chip_side_padding"
         android:contentDescription="@string/ongoing_phone_call_content_description"
         android:minWidth="@dimen/min_clickable_item_size"
     >
 
         <ImageView
             android:src="@*android:drawable/ic_phone"
-            android:layout_width="@dimen/ongoing_call_chip_icon_size"
-            android:layout_height="@dimen/ongoing_call_chip_icon_size"
+            android:layout_width="@dimen/ongoing_activity_chip_icon_size"
+            android:layout_height="@dimen/ongoing_activity_chip_icon_size"
             android:tint="?android:attr/colorPrimary"
         />
 
-        <com.android.systemui.statusbar.phone.ongoingcall.OngoingCallChronometer
-            android:id="@+id/ongoing_call_chip_time"
+        <com.android.systemui.statusbar.chips.ui.view.ChipChronometer
+            android:id="@+id/ongoing_activity_chip_time"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:singleLine="true"
             android:gravity="center|start"
-            android:paddingStart="@dimen/ongoing_call_chip_icon_text_padding"
+            android:paddingStart="@dimen/ongoing_activity_chip_icon_text_padding"
             android:textAppearance="@android:style/TextAppearance.Material.Small"
             android:fontFamily="@*android:string/config_headlineFontFamily"
             android:textColor="?android:attr/colorPrimary"
         />
 
-    </com.android.systemui.statusbar.phone.ongoingcall.OngoingCallBackgroundContainer>
+    </com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer>
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml
index 76f7f3b..49d3a8e 100644
--- a/packages/SystemUI/res/layout/screenshot_shelf.xml
+++ b/packages/SystemUI/res/layout/screenshot_shelf.xml
@@ -33,8 +33,7 @@
             android:layout_width="wrap_content"
             android:elevation="4dp"
             android:background="@drawable/shelf_action_chip_container_background"
-            android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
-            android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin"
+            android:layout_marginHorizontal="@dimen/overlay_action_container_minimum_edge_spacing"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintBottom_toTopOf="@id/guideline"
             >
@@ -61,7 +60,7 @@
             android:id="@+id/screenshot_preview_border"
             android:layout_width="0dp"
             android:layout_height="0dp"
-            android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+            android:layout_marginStart="@dimen/overlay_action_container_minimum_edge_spacing"
             android:layout_marginTop="@dimen/overlay_border_width_neg"
             android:layout_marginEnd="@dimen/overlay_border_width_neg"
             android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin"
@@ -136,11 +135,12 @@
             android:id="@+id/screenshot_scrollable_preview"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:clipToOutline="true"
             android:scaleType="matrix"
             android:visibility="gone"
             app:layout_constraintStart_toStartOf="@id/screenshot_preview"
             app:layout_constraintTop_toTopOf="@id/screenshot_preview"
-            android:elevation="7dp"/>
+            android:elevation="3dp"/>
 
         <androidx.constraintlayout.widget.Guideline
             android:id="@+id/guideline"
@@ -153,7 +153,7 @@
             android:id="@+id/screenshot_message_container"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
-            android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
+            android:layout_marginHorizontal="@dimen/overlay_action_container_minimum_edge_spacing"
             android:layout_marginTop="4dp"
             android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
             android:paddingHorizontal="@dimen/overlay_action_container_padding_end"
@@ -171,6 +171,13 @@
         </FrameLayout>
     </androidx.constraintlayout.widget.ConstraintLayout>
     <ImageView
+        android:id="@+id/screenshot_scrolling_scrim"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone"
+        android:clickable="true"
+        android:importantForAccessibility="no"/>
+    <ImageView
         android:id="@+id/screenshot_flash"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 452bc31..4247c7e 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -99,7 +99,7 @@
                         android:gravity="center_vertical|start"
                     />
 
-                    <include layout="@layout/ongoing_call_chip" />
+                    <include layout="@layout/ongoing_activity_chip" />
 
                     <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
                         android:id="@+id/notification_icon_area"
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 8a1f726..cc8f9c2 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Linkergrens <xliff:g id="PERCENT">%1$d</xliff:g> persent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Regtergrens <xliff:g id="PERCENT">%1$d</xliff:g> persent"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Gestoor in <xliff:g id="APP">%1$s</xliff:g> in die werkprofiel"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Gestoor in <xliff:g id="APP">%1$s</xliff:g> in die privaat profiel"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Lêers"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> het hierdie skermskoot bespeur."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> en ander oop apps het hierdie skermskoot bespeur."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Langdruk om legstukke te pasmaak"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Pasmaak legstukke"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App-ikoon vir gedeaktiveerde legstuk"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Wysig legstuk"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Verwyder"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Voeg legstuk by"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Maak legstukke op sluitskerm toe"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Pasmaak legstukke"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Legstukke op sluitskerm"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"kies legstuk"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"verwyder legstuk"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"plaas gekose legstuk"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Wissel gebruiker"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"aftrekkieslys"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle programme en data in hierdie sessie sal uitgevee word."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Voer uitsetinstellings in"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volumeglyers is uitgevou"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volumeglyers is ingevou"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"demp %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"ontdemp %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> speel tans op"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Oudio sal speel op"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Bel met"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Stelsel-UI-ontvanger"</string>
     <string name="status_bar" msgid="4357390266055077437">"Statusbalk"</string>
     <string name="demo_mode" msgid="263484519766901593">"Stelsel-UI-demonstrasiemodus"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelliet, swak verbinding"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelliet, goeie toestand"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelliet, verbinding is beskikbaar"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satelliet-SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Werkprofiel"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Pret vir party mense, maar nie vir almal nie"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Stelsel-UI-ontvanger gee jou ekstra maniere om die Android-gebruikerkoppelvlak in te stel en te pasmaak. Hierdie eksperimentele kenmerke kan in toekomstige uitreikings verander, breek of verdwyn. Gaan versigtig voort."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Voeg teël by"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Skuif na <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Voeg by posisie <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posisie is ongeldig."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posisie <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Teël is bygevoeg"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Teël is verwyder"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index e3bfff7..b00dc78 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"የግራ ወሰን <xliff:g id="PERCENT">%1$d</xliff:g> በመቶ"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"የቀኝ ወሰን <xliff:g id="PERCENT">%1$d</xliff:g> በመቶ"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"<xliff:g id="APP">%1$s</xliff:g> ውስጥ የስራ መገለጫው ውስጥ ተቀምጧል"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"በግል መገለጫው ውስጥ በ<xliff:g id="APP">%1$s</xliff:g> ውስጥ ተቀምጧል"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ፋይሎች"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ይህን ቅጽበታዊ ገፅ ዕይታ ለይቷል።"</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> እና ሌሎች ክፍት መተግበሪያዎች ይህን ቅጽበታዊ ገፅ ዕይታ ለይተዋል።"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ምግብሮችን ለማበጀት በረጅሙ ይጫኑ"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ምግብሮችን አብጅ"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"የመተግበሪያ አዶ ለተሰናከለ ምግብር"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"ምግብርን አርትዕ"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"አስወግድ"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ምግብር አክል"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"ምግብሮችን በማያ ገጽ ቁልፍ ላይ ዝጋ"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ምግብሮችን አብጅ"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ምግብሮች በማያ ገጽ ቁልፍ ላይ"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ምግብር ይምረጡ"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ምግብር አስወግድ"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"በቦታ የተመረጠ ምግብር"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ተጠቃሚ ቀይር"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ወደታች ተጎታች ምናሌ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"በዚህ ክፍለ-ጊዜ ውስጥ ያሉ ሁሉም መተግበሪያዎች እና ውሂብ ይሰረዛሉ።"</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"የውጽዓት ቅንብሮችን ያስገቡ"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"የድምጽ ተንሸራታቾች ተዘርግቷል"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"የድምጽ ተንሸራታቾች ተሰብስቧል"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s ላይ ድምፀ-ከል አድርግ"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"የ%s ድምፀ-ከል አንሳ"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> እየተጫወተ ያለው በ"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"ኦዲዮ ይጫወታል በ"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"በጥሪ ላይ"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"የስርዓት በይነገጽ መቃኛ"</string>
     <string name="status_bar" msgid="4357390266055077437">"የሁኔታ አሞሌ"</string>
     <string name="demo_mode" msgid="263484519766901593">"የስርዓት ተጠቃሚ በይነገጽ ማሳያ ሁነታ"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"ሳተላይት፣ ደካማ ግንኙነት"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ሳተላይት፣ ጥሩ ግንኙነት"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ሳተላይት፣ ግንኙነት አለ"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ሳተላይት ኤስኦኤስ"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"የስራ መገለጫ"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"ለአንዳንዶች አስደሳች ቢሆንም ለሁሉም አይደለም"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"የስርዓት በይነገጽ መቃኛ የAndroid ተጠቃሚ በይነገጹን የሚነካኩበት እና የሚያበጁበት ተጨማሪ መንገዶች ይሰጠዎታል። እነዚህ የሙከራ ባህሪዎች ወደፊት በሚኖሩ ልቀቶች ላይ ሊለወጡ፣ ሊሰበሩ ወይም ሊጠፉ ይችላሉ። ከጥንቃቄ ጋር ወደፊት ይቀጥሉ።"</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"ሰቅ ያክሉ"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"ወደ <xliff:g id="POSITION">%1$d</xliff:g> ውሰድ"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"ወደ <xliff:g id="POSITION">%1$d</xliff:g> ቦታ አክል"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"አቀማመጡ ተቀባይነት የለውም።"</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"የ<xliff:g id="POSITION">%1$d</xliff:g> አቀማመጥ"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ሰቅ ታክሏል"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ሰቅ ተወግዷል"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index f49d60e..d498154 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"الحد الأيسر <xliff:g id="PERCENT">%1$d</xliff:g> في المئة"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"الحد الأيمن <xliff:g id="PERCENT">%1$d</xliff:g> في المئة"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"تم حفظ لقطة الشاشة في \"<xliff:g id="APP">%1$s</xliff:g>\" في ملف العمل."</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"تم حفظ لقطة الشاشة في \"<xliff:g id="APP">%1$s</xliff:g>\" في الملف الشخصي الخاص."</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"الملفات"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"رصَد تطبيق \"<xliff:g id="APPNAME">%1$s</xliff:g>\" لقطة الشاشة هذه."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"رصَد تطبيق \"<xliff:g id="APPNAME">%1$s</xliff:g>\" والتطبيقات المفتوحة الأخرى لقطة الشاشة هذه."</string>
@@ -163,7 +162,7 @@
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="7918067993953940778">"تمّ التأكيد."</string>
     <string name="biometric_dialog_tap_confirm" msgid="9166350738859143358">"يمكنك النقر على \"تأكيد\" لإكمال المهمة."</string>
     <string name="biometric_dialog_tap_confirm_with_face" msgid="2378151312221818694">"تم فتح قفل جهازك عند تقريبه من وجهك."</string>
-    <string name="biometric_dialog_tap_confirm_with_face_alt_1" msgid="439152621640507113">"تم فتح قفل جهازك عند تقريبه من وجهك. اضغط للمتابعة."</string>
+    <string name="biometric_dialog_tap_confirm_with_face_alt_1" msgid="439152621640507113">"تم فتح الجهاز بالتعرّف على وجهك. اضغط للمتابعة."</string>
     <string name="biometric_dialog_tap_confirm_with_face_alt_2" msgid="8586608186457385108">"تم التعرّف على الوجه. اضغط للمتابعة."</string>
     <string name="biometric_dialog_tap_confirm_with_face_alt_3" msgid="2192670471930606539">"تم التعرّف على الوجه. للمتابعة، اضغط على رمز فتح القفل."</string>
     <string name="biometric_dialog_authenticated" msgid="7337147327545272484">"مصادقة"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"اضغط مع الاستمرار لتخصيص التطبيقات المصغّرة."</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"تخصيص التطبيقات المصغَّرة"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"رمز التطبيق المصغّر غير المفعّل"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"تعديل التطبيق المصغَّر"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"إزالة"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"إضافة تطبيق مصغّر"</string>
@@ -461,7 +462,10 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"إغلاق التطبيقات المصغّرة على شاشة القفل"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"تخصيص التطبيقات المصغَّرة"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"التطبيقات المصغّرة على شاشة القفل"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"اختيار التطبيق المصغّر"</string>
+    <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) -->
+    <skip />
+    <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) -->
     <skip />
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"تبديل المستخدم"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"القائمة المنسدلة"</string>
@@ -627,8 +631,14 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"الدخول إلى إعدادات إخراج الصوت"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"تم توسيع أشرطة تمرير الصوت"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"تم تصغير أشرطة تمرير الصوت"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"‏كتم صوت \"%s\""</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"‏إعادة صوت \"%s\""</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"تشغيل <xliff:g id="LABEL">%s</xliff:g> على"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"سيتم تشغيل الصوت على"</string>
     <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
@@ -660,8 +670,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"قمر صناعي، الاتصال ضعيف"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"قمر صناعي، الاتصال جيد"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"قمر صناعي، الاتصال متوفّر"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"اتصالات الطوارئ بالقمر الصناعي"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"ملف العمل"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"متعة للبعض وليس للجميع"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"‏توفر لك أداة ضبط واجهة مستخدم النظام طرقًا إضافية لتعديل واجهة مستخدم Android وتخصيصها. ويمكن أن تطرأ تغييرات على هذه الميزات التجريبية أو يمكن أن تتعطل هذه الميزات أو تختفي في الإصدارات المستقبلية. عليك متابعة الاستخدام مع توخي الحذر."</string>
@@ -866,8 +875,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"إضافة بطاقة"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"الانتقال إلى <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"الإضافة إلى الموضع <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"الموضِع غير صالح."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"الموضع: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"تمت إضافة البطاقة."</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"تمت إزالة البطاقة."</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 0a4c4db..daefd83 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -447,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ৱিজেট কাষ্টমাইজ কৰিবলৈ দীঘলীয়াকৈ টিপক"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ৱিজেট কাষ্টমাইজ কৰক"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"অক্ষম কৰা ৱিজেটৰ বাবে এপৰ চিহ্ন"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"ৱিজেট সম্পাদনা কৰক"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"আঁতৰাওক"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ৱিজেট যোগ দিয়ক"</string>
@@ -461,6 +463,8 @@
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ৱিজেট কাষ্টমাইজ কৰক"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"লক স্ক্ৰীনত ৱিজেট"</string>
     <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ৱিজেট বাছনি কৰক"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ৱিজেট আঁতৰাওক"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"বাছনি কৰা ৱিজেটটো ৰাখক"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ব্যৱহাৰকাৰী সলনি কৰক"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"পুল-ডাউনৰ মেনু"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই ছেশ্বনৰ আটাইবোৰ এপ্ আৰু ডেটা মচা হ\'ব।"</string>
@@ -625,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"আউটপুট ছেটিং খোলক"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ভলিউমৰ শ্লাইডাৰ বিস্তাৰ কৰা আছে"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ভলিউমৰ শ্লাইডাৰ সংকোচন কৰা আছে"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s মিউট কৰক"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s আনমিউট কৰক"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"ইয়াত <xliff:g id="LABEL">%s</xliff:g> প্লে’ হৈ আছে"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"অডিঅ’ ইয়াত প্লে’ হ’ব"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"কল চলি আছে"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"স্থিতি দণ্ড"</string>
     <string name="demo_mode" msgid="263484519766901593">"ছিষ্টেমৰ UI প্ৰদৰ্শন ম\'ড"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index f4b69b6..399e621 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Sol sərhəd <xliff:g id="PERCENT">%1$d</xliff:g> faiz"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Sağ sərhəd <xliff:g id="PERCENT">%1$d</xliff:g> faiz"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"İş profilində <xliff:g id="APP">%1$s</xliff:g> tətbiqində saxlanıb"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Şəxsi profildə <xliff:g id="APP">%1$s</xliff:g> tətbiqində yadda saxlanılıb"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fayllar"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> bu skrinşotu aşkarladı."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> və digər açıq tətbiqlər bu skrinşotu aşkarladı."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Basıb saxlayaraq vidcetləri fərdiləşdirin"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Vidcetləri fərdiləşdirin"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Deaktiv edilmiş vidcet üçün tətbiq ikonası"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Vidceti redaktə edin"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Silin"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Vidcet əlavə edin"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Kilid ekranında vidcetləri bağlayın"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Vidcetləri fərdiləşdirin"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Kilid ekranındakı vidcetlər"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"vidcet seçin"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"vidceti silin"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"seçilmiş vidceti yerləşdirin"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"aşağı çəkilən menyu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu sessiyada bütün tətbiqlər və data silinəcək."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Çıxış ayarlarını daxil edin"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Səs slayderləri genişləndirildi"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Səs slayderləri yığcamlaşdırıldı"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s seçimini səssiz edin"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s seçimini səssiz rejimdən çıxarın"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> tətbiqində oxudulur"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio oxudulacaq"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Zəng edilir"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"Status paneli"</string>
     <string name="demo_mode" msgid="263484519766901593">"Sistem interfeysi: demorejim"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Peyk, bağlantı zəifdir"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Peyk, bağlantı yaxşıdır"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Peyk, bağlantı var"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Təcili peyk bağlantısı"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"İş profili"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Hamı üçün deyil, bəziləri üçün əyləncəli"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner Android istifadəçi interfeysini dəyişdirmək və fərdiləşdirmək üçün Sizə ekstra yollar təklif edir."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Lövhə əlavə edin"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> mövqeyinə köçürün"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> mövqeyinə əlavə edin"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Mövqe yanlışdır."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> mövqeyi"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Mozaik əlavə edilib"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Mozaik silinib"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 47df2fa..16056660 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Leva ivica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Desna ivica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Sačuvano je u aplikaciji <xliff:g id="APP">%1$s</xliff:g> na poslovnom profilu"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Sačuvano je u aplikaciji <xliff:g id="APP">%1$s</xliff:g> na privatnom profilu"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fajlovi"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"Aplikacija <xliff:g id="APPNAME">%1$s</xliff:g> je otkrila ovaj snimak ekrana."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> i druge otvorene aplikacije su otkrile ovaj snimak ekrana."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Dugi pritisak za prilagođavanje vidžeta"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prilagodi vidžete"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacije za onemogućen vidžet"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Izmeni vidžet"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Ukloni"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodaj vidžet"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Zatvorite vidžete na zaključanom ekranu"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Prilagodite vidžete"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Vidžeti na zaključanom ekranu"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"izaberite vidžet"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"uklonite vidžet"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"postavite izabrani vidžet"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Zameni korisnika"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući meni"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci u ovoj sesiji će biti izbrisani."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Unesite podešavanja izlaznog signala"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Klizači za jačinu zvuka su prošireni"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Klizači za jačinu zvuka su skupljeni"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"isključite zvuk za: %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"uključite zvuk za: %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> se pušta na"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Zvuk se pušta na"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Poziv na uređaju"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Tjuner za korisnički interfejs sistema"</string>
     <string name="status_bar" msgid="4357390266055077437">"Statusna traka"</string>
     <string name="demo_mode" msgid="263484519766901593">"Režim demonstracije za korisnički interfejs sistema"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelit, veza je loša"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, veza je dobra"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, veza je dostupna"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Hitna pomoć preko satelita"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Poslovni profil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Zabava za neke, ali ne za sve"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Tjuner za korisnički interfejs sistema vam pruža dodatne načine za podešavanje i prilagođavanje Android korisničkog interfejsa. Ove eksperimentalne funkcije mogu da se promene, otkažu ili nestanu u budućim izdanjima. Budite oprezni."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Dodajte pločicu"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Premestite na <xliff:g id="POSITION">%1$d</xliff:g>. poziciju"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodajte na <xliff:g id="POSITION">%1$d</xliff:g>. poziciju"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Pozicija je nevažeća."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>. pozicija"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Pločica je dodata"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Pločica je uklonjena"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 8f080f9..89a4701 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Левая граніца: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Правая граніца: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Захавана ў праграму \"<xliff:g id="APP">%1$s</xliff:g>\" (у працоўным профілі)"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Захавана ў праграму \"<xliff:g id="APP">%1$s</xliff:g>\" (у прыватным профілі)"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Файлы"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"Праграма \"<xliff:g id="APPNAME">%1$s</xliff:g>\" выявіла гэты здымак экрана."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> і іншыя адкрытыя праграмы выявілі гэты здымак экрана."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Доўга націскайце, каб наладзіць віджэты"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Наладзіць віджэты"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Значок праграмы для адключанага віджэта"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Змяніць віджэт"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Выдаліць"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Дадаць віджэт"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Закрыць віджэты на экране блакіроўкі"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Наладзіць віджэты"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Віджэты на экране блакіроўкі"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"выбраць віджэт"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"выдаліць віджэт"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"размясціць выбраны віджэт"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Перайсці да іншага карыстальніка"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"высоўнае меню"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усе праграмы і даныя гэтага сеанса будуць выдалены."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Перайсці да налад вываду"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Меню з паўзункамі гучнасці разгорнута"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Меню з паўзункамі гучнасці згорнута"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"выключыць гук (%s)"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"уключыць гук (%s)"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> прайграецца тут:"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Аўдыявыхад:"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Ідзе выклік"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Наладка сістэмнага інтэрфейсу карыстальніка"</string>
     <string name="status_bar" msgid="4357390266055077437">"Панэль стану"</string>
     <string name="demo_mode" msgid="263484519766901593">"Рэжым дэманстрацыі сістэмнага інтэрфейсу карыстальніка"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Спадарожнікавая сувязь, дрэннае падключэнне"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Спадарожнікавая сувязь, добрае падключэнне"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Спадарожнікавая сувязь, падключэнне даступнае"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Экстраннае спадарожнікавае падключэнне"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Працоўны профіль"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Цікава для некаторых, але не для ўсіх"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Наладка сістэмнага інтэрфейсу карыстальніка дае вам дадатковыя спосабы наладжвання і дапасоўвання карыстальніцкага інтэрфейсу Android. Гэтыя эксперыментальныя функцыі могуць змяніцца, перастаць працаваць або знікнуць у будучых версіях. Карыстайцеся з асцярожнасцю."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Дадаць плітку"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Перамясціць на пазіцыю <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Дадаць на пазіцыю <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Няправільнае месца."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Пазіцыя <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Плітка дададзена"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Плітка выдалена"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 08a643d..bdd9fd4 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Лява граница: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Дясна граница: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Запазена в(ъв) <xliff:g id="APP">%1$s</xliff:g> в служебния потребителски профил"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Запазена в(ъв) <xliff:g id="APP">%1$s</xliff:g> в личния потребителски профил"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> установи заснемането на тази екранна снимка."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> и други отворени приложения установиха заснемането на тази екранна снимка."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Натиснете продължително за персонализ. на приспос."</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Персонализиране на приспособленията"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Икона на приложение за деактивирано приспособление"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Редактиране на приспособлението"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Премахване"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Добавяне на приспособление"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Затваряне на приспособленията на заключения екран"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Персонализиране на приспособленията"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Приспособления на заключения екран"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"избиране на приспособление"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"премахване на приспособлението"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"поставяне на избраното приспособление"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Превключване между потребителите"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"падащо меню"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Всички приложения и данни в тази сесия ще бъдат изтрити."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Отваряне на изходните настройки"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Плъзгачите за силата на звука са разгънати"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Плъзгачите за силата на звука са свити"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"спиране на звука на %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"включване на звука на %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Възпроизвеждане на <xliff:g id="LABEL">%s</xliff:g> на"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Аудиото ще се възпроизвежда на"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Активно обаждане"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Тунер на системния потребителски интерфейс"</string>
     <string name="status_bar" msgid="4357390266055077437">"Лента на състоянието"</string>
     <string name="demo_mode" msgid="263484519766901593">"Демонстрационен режим на системния ПИ"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Сателит, лоша връзка"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Сателит, добра връзка"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Сателит, налице е връзка"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS чрез сателит"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Потребителски профил в Work"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Забавно – но не за всички"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Тунерът на системния потребителски интерфейс ви предоставя допълнителни възможности за прецизиране и персонализиране на практическата работа с Android. Тези експериментални функции може да се променят, повредят или да изчезнат в бъдещите версии. Действайте внимателно."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Добавяне на панел"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Преместване към позиция <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Добавяне към позиция <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Невалидна позиция."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Позиция <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Панелът е добавен"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Панелът е премахнат"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index cd00852..2c47b12 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"বাঁ প্রান্ত থেকে <xliff:g id="PERCENT">%1$d</xliff:g> শতাংশ"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ডান প্রান্ত থেকে <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"অফিস প্রোফাইলের মধ্যে <xliff:g id="APP">%1$s</xliff:g>-এ সেভ করা হয়েছে"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"<xliff:g id="APP">%1$s</xliff:g>-এ ব্যক্তিগত প্রোফাইলে সেভ করা হয়েছে"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ফাইল"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g>, এই স্ক্রিনশট শনাক্ত করেছে।"</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> এবং খোলা থাকা অন্য অ্যাপ এই স্ক্রিনশট শনাক্ত করেছে।"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"উইজেট কাস্টমাইজ করতে বেশিক্ষণ প্রেস করুন"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"উইজেট কাস্টমাইজ করুন"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"বন্ধ করা উইজেটের জন্য অ্যাপের আইকন"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"উইজেট এডিট করুন"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"সরান"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"উইজেট যোগ করুন"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"লক স্ক্রিনে উইজেট বন্ধ করুন"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"উইজেট কাস্টমাইজ করুন"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"লক স্ক্রিনে উইজেট"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"উইজেট বেছে নিন"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"উইজেট সরান"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"বেছে নেওয়া উইজেটটি রাখুন"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ব্যবহারকারী পাল্টে দিন"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"পুলডাউন মেনু"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই সেশনের সব অ্যাপ ও ডেটা মুছে ফেলা হবে।"</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"আউটপুট সেটিংস লিখুন"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ভলিউম স্লাইডার বড় করা হয়েছে"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ভলিউম স্লাইডার আড়াল করা হয়েছে"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s মিউট করুন"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s আনমিউট করুন"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g>-এ প্লে করা হচ্ছে"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"অডিও এতে প্লে করা হবে"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"কল চালু আছে"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"সিস্টেম UI টিউনার"</string>
     <string name="status_bar" msgid="4357390266055077437">"স্ট্যাটাস বার"</string>
     <string name="demo_mode" msgid="263484519766901593">"সিস্টেম UI ডেমো মোড"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"স্যাটেলাইট, খারাপ কানেকশন"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"স্যাটেলাইট, ভালো কানেকশন"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"স্যাটেলাইট, কানেকশন উপলভ্য আছে"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"স্যাটেলাইট SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"কাজের প্রোফাইল"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"কিছু ব্যক্তির জন্য মজাদার কিন্তু সকলের জন্য নয়"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"এই পরীক্ষামূলক বৈশিষ্ট্যগুলি ভবিষ্যতের সংস্করণগুলির মধ্যে পরিবর্তিত, বিভাজিত এবং অদৃশ্য হয়ে যেতে পারে৷ সাবধানতার সাথে এগিয়ে যান৷ সিস্টেম UI টিউনার আপনাকে Android ব্যবহারকারী ইন্টারফেসের সূক্ষ্ম সমন্বয় এবং কাস্টমাইজ করার অতিরিক্ত উপায়গুলি প্রদান করে৷"</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"টাইল যোগ করুন"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g>-এ সরান"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"অবস্থান <xliff:g id="POSITION">%1$d</xliff:g>-এ যোগ করুন"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"পজিশন সঠিক নয়।"</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"অবস্থান <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"টাইল যোগ করা হয়েছে"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"টাইল সরানো হয়েছে"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index a0c13f0..8621b43 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Lijeva granica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Desna granica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Sačuvano je u aplikaciji <xliff:g id="APP">%1$s</xliff:g> na radnom profilu"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Sačuvano je u aplikaciji <xliff:g id="APP">%1$s</xliff:g> na privatnom profilu"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fajlovi"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"Aplikacija <xliff:g id="APPNAME">%1$s</xliff:g> je otkrila ovaj snimak ekrana."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"Aplikacija <xliff:g id="APPNAME">%1$s</xliff:g> i druge otvorene aplikacije su otkrile ovaj snimak ekrana."</string>
@@ -154,7 +153,7 @@
     <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Pošalji"</string>
     <string name="cancel" msgid="1089011503403416730">"Otkaži"</string>
     <string name="biometric_dialog_logo" msgid="7681107853070774595">"Logotip aplikacije"</string>
-    <string name="biometric_dialog_confirm" msgid="2005978443007344895">"Potvrdite"</string>
+    <string name="biometric_dialog_confirm" msgid="2005978443007344895">"Potvrdi"</string>
     <string name="biometric_dialog_try_again" msgid="8575345628117768844">"Pokušaj ponovo"</string>
     <string name="biometric_dialog_empty_space_description" msgid="3330555462071453396">"Dodirnite da otkažete autentifikaciju"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4351777022315116816">"Pokušajte ponovo"</string>
@@ -268,7 +267,7 @@
     <string name="quick_settings_bluetooth_label" msgid="7018763367142041481">"Bluetooth"</string>
     <string name="quick_settings_bluetooth_detail_empty_text" msgid="5760239584390514322">"Nema dostupnih uparenih uređaja"</string>
     <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Dodirnite da povežete ili prekinete povezanost uređaja"</string>
-    <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Upari novi uređaj"</string>
+    <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Uparite novi uređaj"</string>
     <string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Prikaži sve"</string>
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Koristi Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pritisnite i zadržite da prilagodite vidžete"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prilagodite vidžete"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacije za onemogućeni vidžet"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Uredite vidžet"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Uklanjanje"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodajte vidžet"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Zatvaranje vidžeta na zaključanom ekranu"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Prilagođavanje vidžeta"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Vidžeti na zaključanom ekranu"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"odabir vidžeta"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"uklanjanje vidžeta"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"postavljanje odabranog vidžeta"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Zamijeni korisnika"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući meni"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci iz ove sesije će se izbrisati."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Unos postavki izlaza"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Klizači jačine zvuka su prošireni"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Klizači jačine zvuka su suženi"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"isključivanje parametra %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"uključivanje parametra %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Reproducira se <xliff:g id="LABEL">%s</xliff:g> na"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Reprodukcija zvuka na"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Poziv putem"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Podešavač za korisnički interfejs sistema"</string>
     <string name="status_bar" msgid="4357390266055077437">"Statusna traka"</string>
     <string name="demo_mode" msgid="263484519766901593">"Demo način rada Sistemskog UI-ja"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelit, slaba veza"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, dobra veza"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, veza je dostupna"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Hitna pomoć putem satelita"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Radni profil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Zabava za neke, ali ne za sve"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Podešavač za korisnički interfejs sistema vam omogućava dodatne načine da podesite i prilagodite Androidov interfejs. Ove eksperimentalne funkcije se u budućim verzijama mogu mijenjati, kvariti ili nestati. Budite oprezni."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Dodavanje kartice"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Pomjeranje u položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodavanje u položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Nevažeći položaj."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kartica je dodana"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kartica je uklonjena"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 0c5d51d..6ebfd86 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Marge esquerre <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Marge dret <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"S\'ha desat al perfil de treball de <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"S\'ha desat a <xliff:g id="APP">%1$s</xliff:g> al perfil privat"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fitxers"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ha detectat aquesta captura de pantalla."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> i altres aplicacions obertes han detectat aquesta captura de pantalla."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantén premut per personalitzar els widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalitza els widgets"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icona de l\'aplicació per a widget desactivat"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Edita el widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Suprimeix"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Afegeix un widget"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Tanca els widgets a la pantalla de bloqueig"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalitza els widgets"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets a la pantalla de bloqueig"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"selecciona el widget"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"suprimeix el widget"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"col·loca el widget seleccionat"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Canvia d\'usuari"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú desplegable"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Totes les aplicacions i les dades d\'aquesta sessió se suprimiran."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Introdueix opcions de configuració de sortida"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Els controls lliscants de volum s\'han desplegat"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Els controls lliscants de volum s\'han replegat"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"silencia %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"deixa de silenciar %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"S\'està reproduint <xliff:g id="LABEL">%s</xliff:g> a"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Es reproduirà a"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Trucant des de"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Personalitzador d\'interfície d\'usuari"</string>
     <string name="status_bar" msgid="4357390266055077437">"Barra d\'estat"</string>
     <string name="demo_mode" msgid="263484519766901593">"Mode de demostració de la IU del sistema"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satèl·lit, connexió deficient"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satèl·lit, bona connexió"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satèl·lit, connexió disponible"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS per satèl·lit"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Perfil de treball"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Diversió per a uns quants, però no per a tothom"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"El Personalitzador d\'interfície d\'usuari presenta opcions addicionals per canviar i personalitzar la interfície d\'usuari d\'Android. És possible que aquestes funcions experimentals canviïn, deixin de funcionar o desapareguin en versions futures. Continua amb precaució."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Afegeix una icona"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mou a la posició <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Afegeix a la posició <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"La posició no és vàlida."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posició <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"El mosaic s\'ha afegit"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"El mosaic s\'ha suprimit"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index f0aa2c0..7d2202d 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Levý okraj <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Pravý okraj <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Uloženo v aplikaci <xliff:g id="APP">%1$s</xliff:g> v pracovním profilu"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Uloženo do aplikace <xliff:g id="APP">%1$s</xliff:g> v soukromém profilu"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Soubory"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"Aplikace <xliff:g id="APPNAME">%1$s</xliff:g> objevila tento snímek obrazovky."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> a ostatní otevřené aplikace objevily tento snímek obrazovky."</string>
@@ -270,15 +269,15 @@
     <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Klepnutím zařízení připojíte nebo odpojíte"</string>
     <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Spárovat nové zařízení"</string>
     <string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Zobrazit vše"</string>
-    <string name="turn_on_bluetooth" msgid="5681370462180289071">"Použít Bluetooth"</string>
+    <string name="turn_on_bluetooth" msgid="5681370462180289071">"Používat Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Připojeno"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Sdílení zvuku"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Uloženo"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"odpojit"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivovat"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Zítra znovu automaticky zapnout"</string>
-    <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Funkce jako Quick Share a Najdi moje zařízení využívají Bluetooth"</string>
-    <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth se zapne zítra ráno"</string>
+    <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Bluetooth využívají funkce jako Quick Share a Najdi moje zařízení."</string>
+    <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth se zapne zítra ráno."</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"Sdílení zvuku"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="8626191139359072540">"Sdílení zvuku"</string>
     <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Baterie: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Dlouhým stisknutím můžete přizpůsobit widgety"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Přizpůsobit widgety"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikace s deaktivovaným widgetem"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Upravit widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Odstranit"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Přidat widget"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Zavřít widgety na obrazovce uzamčení"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Přizpůsobit widgety"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgety na obrazovce uzamčení"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"vybrat widget"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"odstranit widget"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"umístit vybraný widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Přepnout uživatele"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rozbalovací nabídka"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Veškeré aplikace a data v této relaci budou vymazána."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Otevřít nastavení výstupu"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Posuvníky hlasitosti jsou rozbalené"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Posuvníky hlasitosti jsou sbalené"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"ztlumíte %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"zapnete zvuk %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Přehrávání <xliff:g id="LABEL">%s</xliff:g> přes"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Zvuk se bude přehrávat přes"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Volání na zařízení"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Nástroj na ladění uživatelského rozhraní systému"</string>
     <string name="status_bar" msgid="4357390266055077437">"Stavový řádek"</string>
     <string name="demo_mode" msgid="263484519766901593">"Ukázkový režim uživatelského rozhraní systému"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelit, špatné připojení"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, dobré připojení"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, připojení je k dispozici"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS přes satelit"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Pracovní profil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Zábava, která není pro každého"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Nástroj na ladění uživatelského rozhraní systému vám nabízí další způsoby, jak si vyladit a přizpůsobit uživatelské rozhraní Android. Tyto experimentální funkce mohou v dalších verzích chybět, nefungovat nebo být změněny. Postupujte proto prosím opatrně."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Přidat dlaždici"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Přesunout na pozici <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Přidat dlaždici na pozici <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Pozice není platná."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozice <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Karta byla přidána"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Karta byla odstraněna"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 5191ef2..ec03938 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Venstre kant: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Højre kant: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Gemt i <xliff:g id="APP">%1$s</xliff:g> på arbejdsprofilen"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Gemt i <xliff:g id="APP">%1$s</xliff:g> på den private profil"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Filer"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> har registreret dette screenshot."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> og andre åbne apps har registreret dette screenshot."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Hold fingeren nede for at tilpasse widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Tilpas widgets"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Appikon for deaktiveret widget"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Rediger widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Fjern"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Tilføj widget"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Luk widgets på låseskærmen"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Tilpas widgets"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets på låseskærmen"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"vælg widget"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"fjern widget"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"placer valgt widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Skift bruger"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullemenu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apps og data i denne session slettes."</string>
@@ -592,7 +594,7 @@
     <string name="screen_pinning_negative" msgid="6882816864569211666">"Nej tak"</string>
     <string name="screen_pinning_start" msgid="7483998671383371313">"Appen er fastgjort"</string>
     <string name="screen_pinning_exit" msgid="4553787518387346893">"Appen er frigjort"</string>
-    <string name="stream_voice_call" msgid="7468348170702375660">"Ring op"</string>
+    <string name="stream_voice_call" msgid="7468348170702375660">"Opkald"</string>
     <string name="stream_system" msgid="7663148785370565134">"System"</string>
     <string name="stream_ring" msgid="7550670036738697526">"Ringetone"</string>
     <string name="stream_music" msgid="2188224742361847580">"Medie"</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Angiv indstillinger for output"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Lydstyrkeskydere er udvidet"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Lydstyrkeskydere er skjult"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"slå lyden fra for %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"slå lyden til for %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Afspiller <xliff:g id="LABEL">%s</xliff:g> på"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Lyden afspilles på"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Ringer på"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"Statusbjælke"</string>
     <string name="demo_mode" msgid="263484519766901593">"Demotilstand for systemets brugerflade"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satellit – dårlig forbindelse"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellit – god forbindelse"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellit – forbindelsen er tilgængelig"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS-meldinger via satellit"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Arbejdsprofil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Sjovt for nogle, men ikke for alle"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner giver dig flere muligheder for at justere og tilpasse Android-brugerfladen. Disse eksperimentelle funktioner kan ændres, gå i stykker eller forsvinde i fremtidige udgivelser. Vær forsigtig, hvis du fortsætter."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Tilføj felt"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Flyt til <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Føj til lokation <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Positionen er ugyldig."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Lokation <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Feltet blev tilføjet"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Feltet blev fjernet"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index b427f34..d38ad3a 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Linker Rand <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Rechter Rand <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"In <xliff:g id="APP">%1$s</xliff:g> im Arbeitsprofil gespeichert"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Im vertraulichen Profil in <xliff:g id="APP">%1$s</xliff:g> gespeichert"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Dateien"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> hat diesen Screenshot erkannt."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> und andere geöffnete Apps haben diesen Screenshot erkannt."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Lange drücken, um Widgets anzupassen"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Widgets anpassen"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App-Symbol für deaktiviertes Widget"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Widget bearbeiten"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Entfernen"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Widget hinzufügen"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Widgets auf dem Sperrbildschirm schließen"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Widgets anpassen"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets auf dem Sperrbildschirm"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"Widget auswählen"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"Widget entfernen"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ausgewähltes Widget in den Bearbeitungsmodus versetzen"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Nutzer wechseln"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"Pull-down-Menü"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle Apps und Daten in dieser Sitzung werden gelöscht."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Ausgabeeinstellungen angeben"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Lautstärkeregler maximiert"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Lautstärkeregler minimiert"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s stummzuschalten"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"Stummschaltung von %s aufzuheben"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Wiedergabe von <xliff:g id="LABEL">%s</xliff:g> über"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audiowiedergabe über"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Anruf auf"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"Statusleiste"</string>
     <string name="demo_mode" msgid="263484519766901593">"Demomodus der System-UI"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satellit, Verbindung schlecht"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellit, Verbindung gut"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellit, Verbindung verfügbar"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Notruf über Satellit"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Arbeitsprofil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Für einige ein Vergnügen, aber nicht für alle"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Mit System UI Tuner erhältst du zusätzliche Möglichkeiten, die Android-Benutzeroberfläche anzupassen. Achtung: Diese Testfunktionen können sich ändern, abstürzen oder in zukünftigen Versionen verschwinden."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Kachel hinzufügen"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Auf Position <xliff:g id="POSITION">%1$d</xliff:g> verschieben"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Zur Position <xliff:g id="POSITION">%1$d</xliff:g> hinzufügen"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position ist ungültig."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Ansicht hinzugefügt"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Ansicht entfernt"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 8e3a0ea..c6dad44 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Αριστερό όριο <xliff:g id="PERCENT">%1$d</xliff:g> τοις εκατό"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Δεξί όριο <xliff:g id="PERCENT">%1$d</xliff:g> τοις εκατό"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Αποθηκεύτηκε στην εφαρμογή <xliff:g id="APP">%1$s</xliff:g> στο προφίλ εργασίας"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Αποθηκεύτηκε στην εφαρμογή <xliff:g id="APP">%1$s</xliff:g> στο ιδιωτικό προφίλ"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Αρχεία"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"Η εφαρμογή <xliff:g id="APPNAME">%1$s</xliff:g> εντόπισε αυτό το στιγμιότυπο οθόνης."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"Η εφαρμογή <xliff:g id="APPNAME">%1$s</xliff:g> και άλλες ανοικτές εφαρμογές εντόπισαν το στιγμιότυπο οθόνης."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Παρατεταμένο πάτημα για προσαρμογή γραφ. στοιχείων"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Προσαρμογή γραφικών στοιχείων"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Εικονίδιο εφαρμογής για απενεργοποιημένο γραφικό στοιχείο"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Επεξεργασία γραφικού στοιχείου"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Κατάργηση"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Προσθήκη γραφικού στοιχείου"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Κλείσιμο γραφικών στοιχείων στην οθόνη κλειδώματος"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Προσαρμογή γραφικών στοιχείων"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Γραφικά στοιχεία στην οθόνη κλειδώματος"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"επιλογή γραφικού στοιχείου"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"κατάργηση γραφικού στοιχείου"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"τοποθέτηση επιλεγμένου γραφικού στοιχείου"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Εναλλαγή χρήστη"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"αναπτυσσόμενο μενού"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Όλες οι εφαρμογές και τα δεδομένα αυτής της περιόδου σύνδεσης θα διαγραφούν."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Εισαγωγή ρυθμίσεων εξόδου"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Τα ρυθμιστικά έντασης ήχου αναπτύχθηκαν"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Τα ρυθμιστικά έντασης ήχου συμπτύχθηκαν"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"σίγαση %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"κατάργηση σίγασης %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Αναπαραγωγή <xliff:g id="LABEL">%s</xliff:g> σε"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Ο ήχος θα παίξει σε"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Ενεργή κλήση σε"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"Γραμμή κατάστασης"</string>
     <string name="demo_mode" msgid="263484519766901593">"Λειτουργία επίδειξης διεπαφής χρήστη συστήματος"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Δορυφορική, κακή σύνδεση"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Δορυφορική, καλή σύνδεση"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Δορυφορική, διαθέσιμη σύνδεση"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Δορυφορικό SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Προφίλ εργασίας"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Διασκέδαση για ορισμένους, αλλά όχι για όλους"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Το System UI Tuner σάς προσφέρει επιπλέον τρόπους για να τροποποιήσετε και να προσαρμόσετε τη διεπαφή χρήστη Android. Αυτές οι πειραματικές λειτουργίες ενδέχεται να τροποποιηθούν, να παρουσιάσουν σφάλματα ή να καταργηθούν σε μελλοντικές εκδόσεις. Συνεχίστε με προσοχή."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Προσθήκη πλακιδίου"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Μετακίνηση στη θέση <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Προσθήκη στη θέση <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Μη έγκυρη θέση."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Θέση <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Το πλακίδιο προστέθηκε"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Το πλακίδιο καταργήθηκε"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index e24ec05..cbb2298 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Left boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Right boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Saved in <xliff:g id="APP">%1$s</xliff:g> in the work profile"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Saved in <xliff:g id="APP">%1$s</xliff:g> in the private profile"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> detected this screenshot."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> and other open apps detected this screenshot."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customise widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Customise widgets"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App icon for disabled widget"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Close widgets on lock screen"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Customise widgets"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets on lock screen"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"select widget"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"remove widget"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"place selected widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Enter output settings"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volume sliders expanded"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volume sliders collapsed"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"mute %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"unmute %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Playing <xliff:g id="LABEL">%s</xliff:g> on"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio will play on"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Calling on"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"Status bar"</string>
     <string name="demo_mode" msgid="263484519766901593">"System UI demo mode"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satellite, poor connection"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellite, good connection"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellite, connection available"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Work profile"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Fun for some but not for all"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner gives you extra ways to tweak and customise the Android user interface. These experimental features may change, break or disappear in future releases. Proceed with caution."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Add tile"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Move to <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Add to position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position invalid."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tile added"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tile removed"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 366fc1f..fa007b6 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -447,6 +447,7 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customize widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Customize widgets"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App icon for disabled widget"</string>
+    <string name="icon_description_for_pending_widget" msgid="8413816401868001755">"App icon for a widget being installed"</string>
     <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string>
@@ -461,6 +462,8 @@
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Customize widgets"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets on lock screen"</string>
     <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"select widget"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"remove widget"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"place selected widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
@@ -625,8 +628,10 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Enter output settings"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volume sliders expanded"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volume sliders collapsed"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"mute %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"unmute %s"</string>
+    <string name="volume_panel_hint_mute" msgid="2153922288568199077">"Mute %s"</string>
+    <string name="volume_panel_hint_unmute" msgid="4831850937582282340">"Unmute %s"</string>
+    <string name="volume_panel_hint_muted" msgid="1124844870181285320">"muted"</string>
+    <string name="volume_panel_hint_vibrate" msgid="4136223145435914132">"vibrate"</string>
     <string name="media_output_label_title" msgid="872824698593182505">"Playing <xliff:g id="LABEL">%s</xliff:g> on"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio will play on"</string>
     <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Calling on"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index e24ec05..cbb2298 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Left boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Right boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Saved in <xliff:g id="APP">%1$s</xliff:g> in the work profile"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Saved in <xliff:g id="APP">%1$s</xliff:g> in the private profile"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> detected this screenshot."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> and other open apps detected this screenshot."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customise widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Customise widgets"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App icon for disabled widget"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Close widgets on lock screen"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Customise widgets"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets on lock screen"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"select widget"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"remove widget"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"place selected widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Enter output settings"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volume sliders expanded"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volume sliders collapsed"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"mute %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"unmute %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Playing <xliff:g id="LABEL">%s</xliff:g> on"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio will play on"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Calling on"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"Status bar"</string>
     <string name="demo_mode" msgid="263484519766901593">"System UI demo mode"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satellite, poor connection"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellite, good connection"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellite, connection available"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Work profile"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Fun for some but not for all"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner gives you extra ways to tweak and customise the Android user interface. These experimental features may change, break or disappear in future releases. Proceed with caution."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Add tile"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Move to <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Add to position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position invalid."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tile added"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tile removed"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index e24ec05..cbb2298 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Left boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Right boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Saved in <xliff:g id="APP">%1$s</xliff:g> in the work profile"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Saved in <xliff:g id="APP">%1$s</xliff:g> in the private profile"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> detected this screenshot."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> and other open apps detected this screenshot."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customise widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Customise widgets"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App icon for disabled widget"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Close widgets on lock screen"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Customise widgets"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets on lock screen"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"select widget"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"remove widget"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"place selected widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Enter output settings"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volume sliders expanded"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volume sliders collapsed"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"mute %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"unmute %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Playing <xliff:g id="LABEL">%s</xliff:g> on"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio will play on"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Calling on"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"Status bar"</string>
     <string name="demo_mode" msgid="263484519766901593">"System UI demo mode"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satellite, poor connection"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellite, good connection"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellite, connection available"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Work profile"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Fun for some but not for all"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner gives you extra ways to tweak and customise the Android user interface. These experimental features may change, break or disappear in future releases. Proceed with caution."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Add tile"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Move to <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Add to position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position invalid."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tile added"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tile removed"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 2aa82e0..248360b 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -447,6 +447,7 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‎‏‎‎‏‏‎‏‏‏‏‎‎‏‎‏‎‏‏‏‎‎‏‎‎‎‎‏‎‎‏‏‎‏‎‏‎‎‎‎‎‏‏‎Long press to customize widgets‎‏‎‎‏‎"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‏‎‏‏‎‎‎‏‏‏‏‏‎‏‎‎‎‎‏‏‎‏‏‎‎‎‎‏‎‎‎‏‏‏‎‎‏‎‏‏‎‏‏‏‏‏‏‏‎‎‎‏‎‏‎Customize widgets‎‏‎‎‏‎"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‏‎‏‏‎‎‎‏‏‏‏‎‏‏‏‎‏‎‎‏‎‏‎‎‎‏‎‏‎‏‏‎‏‏‎‎‎‏‎‎‎‏‏‏‏‏‎App icon for disabled widget‎‏‎‎‏‎"</string>
+    <string name="icon_description_for_pending_widget" msgid="8413816401868001755">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‎‏‏‏‏‏‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‏‎‏‏‏‎‏‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‏‎‏‏‎‏‏‎App icon for a widget being installed‎‏‎‎‏‎"</string>
     <string name="edit_widget" msgid="9030848101135393954">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‎‎‎‎‏‎‎‎‎‎‏‎‏‎‏‏‏‏‏‎‏‎‎‏‎‎‏‎‏‎‎‎‏‎‎Edit widget‎‏‎‎‏‎"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‎‎‏‎‏‎‏‏‎‏‎‏‏‏‎‏‎‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎Remove‎‏‎‎‏‎"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‎‎‏‏‎‎‏‏‎‎‏‏‏‏‏‎‎‎‎‎‏‎‏‏‎‏‏‏‎‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‎‏‎Add widget‎‏‎‎‏‎"</string>
@@ -461,6 +462,8 @@
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‎‎‏‎‏‎‎‎‎‎‎‎‎‏‎‎‏‏‏‎‎‏‏‏‎‎‎‎‎‏‎‏‏‏‏‏‏‎‎‎‏‎‏‏‎‎‎‎‏‎‏‎‏‎‎Customize widgets‎‏‎‎‏‎"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‎‎‏‎‏‏‎‏‏‏‎‏‎‎‎‏‎‎‎‎‎‎‎‏‎‏‎‏‏‏‎‎‎‏‏‎‎‎‎‏‎‏‏‎‎‎‎‎‏‎‎‏‎‏‏‎‎Widgets on lock screen‎‏‎‎‏‎"</string>
     <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‏‎‏‏‏‏‏‏‎‎‏‏‎‎‎‏‎‏‎‏‏‏‏‎‎‏‏‎‏‏‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‏‏‏‏‎select widget‎‏‎‎‏‎"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‎‏‎‎‏‎‎‎‎‏‎‎‏‎‏‎‎‎‏‎‎‎‎‏‎‎‏‎‏‏‏‎‏‏‎‎‏‎‎‏‏‎‎‏‏‏‎‎‏‏‎‏‏‎‎remove widget‎‏‎‎‏‎"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‎‎‎‎‏‎‎‏‏‎‎‎‎‏‏‏‎‎‏‎‎‏‏‎‎‎‎‎‎‏‏‏‎‎‎‎‏‏‏‏‎‎‎‏‎‎‎‏‎‎‏‎‎place selected widget‎‏‎‎‏‎"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‏‏‏‎‎‏‏‎‎‎‏‏‏‎‏‎‎‎‎‏‏‎‎‏‎‎‎‎‎‏‏‏‎‏‎‏‏‏‎‏‏‏‎‎‎‎‏‎‏‏‎‏‎‎‎Switch user‎‏‎‎‏‎"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‎‏‏‎‏‎‎‏‏‎‎‎‎‎‎‎‏‏‏‏‎‎‏‎‏‎‎‏‏‎‎‎‎‏‏‏‏‏‏‏‎pulldown menu‎‏‎‎‏‎"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‎‏‎‏‎‎‎‎‎‏‎‎‏‎‏‏‎‎‏‏‎‏‎‎‏‏‏‎‏‏‏‏‎‏‎‏‏‏‎‏‎All apps and data in this session will be deleted.‎‏‎‎‏‎"</string>
@@ -625,8 +628,10 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‏‎‎‎‎‎‎‎‏‏‎‏‏‎‎‎‏‏‏‎‎‏‏‏‏‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‎‏‏‎‏‏‏‎‏‎Enter output settings‎‏‎‎‏‎"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‎‎‏‎‏‎‏‏‏‎‎‎‏‎‎‎‏‎‎‎‏‎‎‎‎‏‎‎‎‎‎‎‏‏‎‎‏‎‎‎‏‎‏‎‎‏‏‏‏‎‎‏‏‏‏‏‎Volume sliders expanded‎‏‎‎‏‎"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‎‎‏‏‏‎‏‎‏‎‏‏‎‎‏‎‎‎‎‏‎‏‎‎‏‏‏‎‎‎‎‎‏‏‎‏‎‏‎‏‎‏‏‏‎‏‎‎‎‎‏‏‎‏‎‎Volume sliders collapsed‎‏‎‎‏‎"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‏‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‎‎‏‏‏‎‏‏‎‏‎‎mute %s‎‏‎‎‏‎"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‎‏‏‎‏‏‏‏‎‎‎‏‎‏‏‎‏‏‎‎‎‏‎‎‏‎‎‎‏‏‎‎unmute %s‎‏‎‎‏‎"</string>
+    <string name="volume_panel_hint_mute" msgid="2153922288568199077">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‏‎‎‎‏‎‎‏‏‏‎‏‎‎‎‏‏‎‏‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‎‎‏‏‏‏‎‏‎‎‏‎‏‎Mute %s‎‏‎‎‏‎"</string>
+    <string name="volume_panel_hint_unmute" msgid="4831850937582282340">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‎‎‏‏‏‎‎‎‏‎‏‏‏‎‏‏‎‏‎‎‏‏‏‏‎‏‎‏‏‎‎‎‏‏‏‎‏‎‏‏‏‎‎‏‏‎‎‏‏‎‎‏‎‎‎Unmute %s‎‏‎‎‏‎"</string>
+    <string name="volume_panel_hint_muted" msgid="1124844870181285320">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‏‎‎‏‏‏‎‎‎‏‎‎‎‎‎‎‎‏‏‏‎‎‏‏‎‎‏‎‎‎‏‎‏‎‎‏‎‎‏‏‎‏‏‎‏‏‎‏‏‏‎‎‏‎‎‎‎muted‎‏‎‎‏‎"</string>
+    <string name="volume_panel_hint_vibrate" msgid="4136223145435914132">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‏‎‎‏‏‎‏‏‎‏‎‎‎‏‎‎‎‎‏‎‎‏‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‎‏‏‏‏‏‏‎‎‏‎‏‎‎‎vibrate‎‏‎‎‏‎"</string>
     <string name="media_output_label_title" msgid="872824698593182505">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‎‎‏‏‏‎‎‏‏‏‎‎‏‎‏‎‏‏‏‎‎‎‎‎‏‎‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‎‎‎‎‏‏‎‎‏‎‏‎‎‏‎Playing ‎‏‎‎‏‏‎<xliff:g id="LABEL">%s</xliff:g>‎‏‎‎‏‏‏‎ on‎‏‎‎‏‎"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‎‎‏‏‎‏‏‎‎‏‏‏‎‎‎‎‎‏‎‏‏‎‏‎‏‏‎‏‎‎‎‎‎‏‏‎‏‏‎‏‎‏‎Audio will play on‎‏‎‎‏‎"</string>
     <string name="media_output_title_ongoing_call" msgid="208426888064112006">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‏‏‏‎‎‏‎‎‎‏‏‏‏‎‏‏‎‎‏‎‎‏‏‎‏‎‎‎‎‎‎‎‏‎‏‏‎‎‏‏‏‎‎‏‎‏‎‏‏‎‎‎‎‏‏‎‎Calling on‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index e01c327..28230f1 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Límite izquierdo: <xliff:g id="PERCENT">%1$d</xliff:g> por ciento"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Límite derecho: <xliff:g id="PERCENT">%1$d</xliff:g> por ciento"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Se guardó en <xliff:g id="APP">%1$s</xliff:g> en el perfil de trabajo"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Guardada en <xliff:g id="APP">%1$s</xliff:g> en el perfil personal"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Archivos"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> detectó que tomaste una captura de pantalla."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> y otras apps en ejecución detectaron que tomaste una captura de pantalla."</string>
@@ -276,7 +275,7 @@
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Guardado"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activar"</string>
-    <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Volver a activar automáticamente mañana"</string>
+    <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Activar automáticamente mañana"</string>
     <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Las funciones como Quick Share y Encontrar mi dispositivo usan Bluetooth"</string>
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"El Bluetooth se activará mañana a la mañana"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"Uso compartido de audio"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantén presionado para personalizar los widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ícono de la app de widget inhabilitado"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Modificar widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Quitar"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Agregar widget"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Cerrar widgets en la pantalla de bloqueo"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizar widgets"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets en la pantalla de bloqueo"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"Seleccionar widget"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"quitar widget"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"colocar widget seleccionado"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar usuario"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú expandible"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán las aplicaciones y los datos de esta sesión."</string>
@@ -617,7 +619,7 @@
     <string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"Audio espacial"</string>
     <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Desactivar"</string>
     <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Fijar"</string>
-    <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Seg. de cabeza"</string>
+    <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Monitoreo de cabeza"</string>
     <string name="volume_ringer_change" msgid="3574969197796055532">"Presiona para cambiar el modo de timbre"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"silenciar"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"dejar de silenciar"</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Ingresar configuración de salida"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Controles deslizantes del volumen expandidos"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Controles deslizantes del volumen colapsados"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"silenciar %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"activar sonido %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Reproduciendo <xliff:g id="LABEL">%s</xliff:g> en"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Se reproducirá en"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Llamando en"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Sintonizador de IU del sistema"</string>
     <string name="status_bar" msgid="4357390266055077437">"Barra de estado"</string>
     <string name="demo_mode" msgid="263484519766901593">"Modo demostración de la IU del sistema"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satélite, conexión inestable"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, buena conexión"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, conexión disponible"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS por satélite"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Perfil de trabajo"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Diversión para algunos, pero no para todos"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"El sintonizador de IU del sistema te brinda más formas para editar y personalizar la interfaz de usuario de Android. Estas funciones experimentales pueden cambiar, dejar de funcionar o no incluirse en futuras versiones. Procede con precaución."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Agregar tarjeta"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mover a <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Agregar a la posición <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posición no válida"</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posición <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Se agregó la tarjeta"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Se quitó la tarjeta"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index e6d3599..fd2ac85 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -447,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantén pulsado para personalizar los widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icono de la aplicación de widget inhabilitado"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Quitar"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Añadir widget"</string>
@@ -461,6 +463,8 @@
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizar widgets"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets en la pantalla de bloqueo"</string>
     <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"seleccionar widget"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"eliminar widget"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"colocar widget seleccionado"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar de usuario"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú desplegable"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán todas las aplicaciones y datos de esta sesión."</string>
@@ -625,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Introducir los ajustes de salida"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Controles deslizantes de volumen desplegados"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Controles deslizantes de volumen contraídos"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"silenciar %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"dejar de silenciar %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Reproduciendo <xliff:g id="LABEL">%s</xliff:g> en"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Se reproducirá en"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Llamando desde"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Configurador de UI del sistema"</string>
     <string name="status_bar" msgid="4357390266055077437">"Barra de estado"</string>
     <string name="demo_mode" msgid="263484519766901593">"Modo Demo de UI del sistema"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 65b1908c..7f97176 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Vasak piir: <xliff:g id="PERCENT">%1$d</xliff:g> protsenti"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Parem piir: <xliff:g id="PERCENT">%1$d</xliff:g> protsenti"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Salvestati tööprofiilil rakendusse <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Salvestati privaatsel profiilil rakendusse <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> tuvastas selle ekraanipildi."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> ja muud avatud rakendused tuvastasid selle ekraanipildi."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Vajutage pikalt vidinate kohandamiseks"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Kohanda vidinaid"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Keelatud vidina rakenduseikoon"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Muuda vidinat"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Eemalda"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Lisa vidin"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Lukustuskuva vidinate sulgemine"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Kohanda vidinaid"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Lukustuskuva vidinad"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"vidina valimine"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"eemaldage vidin"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"asetage valitud vidin"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Kasutaja vahetamine"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rippmenüü"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Seansi kõik rakendused ja andmed kustutatakse."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Sisestage väljundseaded"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Helitugevuse liugurid laiendatud"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Helitugevuse liugurid ahendatud"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"vaigistab %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"tühistab %s vaigistuse"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Esitamine jätkub seadmes <xliff:g id="LABEL">%s</xliff:g>"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Heli esitatakse seadmes"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Helistamine seadmes"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Süsteemi kasutajaliidese tuuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"Olekuriba"</string>
     <string name="demo_mode" msgid="263484519766901593">"Süsteemi kasutajaliidese demorežiim"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelliit, kehv ühendus"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelliit, hea ühendus"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelliit, ühendus on saadaval"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satelliit-SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Tööprofiil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Kõik ei pruugi sellest rõõmu tunda"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Süsteemi kasutajaliidese tuuner pakub täiendavaid võimalusi Androidi kasutajaliidese muutmiseks ja kohandamiseks. Need katselised funktsioonid võivad muutuda, rikki minna või tulevastest versioonidest kaduda. Olge jätkamisel ettevaatlik."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Lisa paan"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Teisaldamine asendisse <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Lisamine asendisse <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Sobimatu asukoht."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Asend <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Paan on lisatud"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Paan on eemaldatud"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 2339847..685b592 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Ezkerreko ertza: ehuneko <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Eskuineko ertza: ehuneko <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Laneko profilaren <xliff:g id="APP">%1$s</xliff:g> aplikazioan gorde da"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Profil pribatuaren <xliff:g id="APP">%1$s</xliff:g> aplikazioan gorde da"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fitxategiak"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> aplikazioak pantaila-argazkia hauteman du."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> aplikazioak eta irekitako beste aplikazio batzuek pantaila-argazkia hauteman dute."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Widgetak pertsonalizatzeko, sakatu luze"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Pertsonalizatu widgetak"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Desgaitutako widgetaren aplikazio-ikonoa"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Editatu widgeta"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Kendu"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Gehitu widget bat"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Itxi pantaila blokeatuko widgetak"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Pertsonalizatu widgetak"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Pantaila blokeatuko widgetak"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"hautatu widget bat"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"kendu widgeta"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"kokatu hautatutako widgeta"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Aldatu erabiltzailea"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"zabaldu menua"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Saioko aplikazio eta datu guztiak ezabatuko dira."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Ireki emaitzaren ezarpenak"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Bolumenaren botoi lerrakorrak zabalduta daude"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Bolumenaren botoi lerrakorrak tolestuta daude"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"desaktibatu honen audioa: %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"aktibatu honen audioa: %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> hemen erreproduzitzen:"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audioak abian jarraituko du hemen:"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Honen bidez deitzen"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Sistemaren erabiltzaile-interfazearen konfiguratzailea"</string>
     <string name="status_bar" msgid="4357390266055077437">"Egoera-barra"</string>
     <string name="demo_mode" msgid="263484519766901593">"Sistemaren erabiltzaile-interfazearen demo modua"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelitea, konexio ahula"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelitea, konexio ona"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelitea, konexioa erabilgarri"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satelite bidezko SOS komunikazioa"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Laneko profila"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Dibertsioa batzuentzat, baina ez guztientzat"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Sistemaren erabiltzaile-interfazearen konfiguratzaileak Android erabiltzaile-interfazea moldatzeko eta pertsonalizatzeko modu gehiago eskaintzen dizkizu. Baliteke eginbide esperimental horiek hurrengo kaleratzeetan aldatuta, etenda edo desagertuta egotea. Kontuz erabili."</string>
@@ -776,8 +782,8 @@
     <string name="group_system_go_back" msgid="2730322046244918816">"Egin atzera"</string>
     <string name="group_system_access_home_screen" msgid="4130366993484706483">"Joan orri nagusira"</string>
     <string name="group_system_overview_open_apps" msgid="5659958952937994104">"Ikusi azkenaldiko aplikazioak"</string>
-    <string name="group_system_cycle_forward" msgid="5478663965957647805">"Ikusi azken aplikazioak banan-banan (aurrerantz)"</string>
-    <string name="group_system_cycle_back" msgid="8194102916946802902">"Ikusi azken aplikazioak banan-banan (atzerantz)"</string>
+    <string name="group_system_cycle_forward" msgid="5478663965957647805">"Ikusi azkenaldiko aplikazioak banan-banan (aurrerantz)"</string>
+    <string name="group_system_cycle_back" msgid="8194102916946802902">"Ikusi azkenaldiko aplikazioak banan-banan (atzerantz)"</string>
     <string name="group_system_access_all_apps_search" msgid="1553588630154197469">"Ireki aplikazioen zerrenda"</string>
     <string name="group_system_access_system_settings" msgid="8731721963449070017">"Ireki ezarpenak"</string>
     <string name="group_system_access_google_assistant" msgid="7210074957915968110">"Ireki Laguntzailea"</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Gehitu lauza"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Eraman <xliff:g id="POSITION">%1$d</xliff:g>garren lekura"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Gehitu <xliff:g id="POSITION">%1$d</xliff:g>garren lekuan"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Kokapenak ez du balio."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>garren lekua"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Gehitu da lauza"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kendu da lauza"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index ad22bcc..d21fdd3 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"مرز سمت چپ <xliff:g id="PERCENT">%1$d</xliff:g> درصد"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"مرز سمت راست <xliff:g id="PERCENT">%1$d</xliff:g> درصد"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"در برنامه <xliff:g id="APP">%1$s</xliff:g> در نمایه کاری ذخیره شد"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"در برنامه <xliff:g id="APP">%1$s</xliff:g> در نمایه خصوصی ذخیره شد"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"‫«<xliff:g id="APPNAME">%1$s</xliff:g>» این نماگرفت را تشخیص داد."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> و سایر برنامه‌های باز این نماگرفت را تشخیص دادند."</string>
@@ -189,7 +188,7 @@
     <string name="biometric_dialog_last_pin_attempt_before_wipe_profile" msgid="545567685899091757">"اگر در تلاش بعدی‌ پین نادرستی وارد کنید، نمایه کاری شما و داده‌های آن حذف خواهند شد."</string>
     <string name="biometric_dialog_last_password_attempt_before_wipe_profile" msgid="8538032972389729253">"اگر در تلاش بعدی‌ گذرواژه نادرستی وارد کنید، نمایه کاری شما و داده‌های آن حذف خواهند شد."</string>
     <string name="biometric_re_enroll_dialog_confirm" msgid="3049858021857801836">"راه‌اندازی"</string>
-    <string name="biometric_re_enroll_dialog_cancel" msgid="93760939407091417">"اکنون نه"</string>
+    <string name="biometric_re_enroll_dialog_cancel" msgid="93760939407091417">"حالا نه"</string>
     <string name="biometric_re_enroll_notification_content" msgid="8685925877186288180">"این کار برای بهبود امنیت و عملکرد لازم است"</string>
     <string name="fingerprint_re_enroll_notification_title" msgid="4539432429683916604">"راه‌اندازی مجدد «قفل‌گشایی با اثر انگشت»"</string>
     <string name="fingerprint_re_enroll_notification_name" msgid="630798657797645704">"قفل‌گشایی با اثر انگشت"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"برای سفارشی‌سازی ابزارک‌ها، فشار طولانی دهید"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"سفارشی‌سازی ابزارک‌ها"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"نماد برنامه برای ابزارک غیرفعال"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"ویرایش ابزارک"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"برداشتن"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"افزودن ابزارک"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"بستن ابزارک‌ها در صفحه قفل"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"سفارشی‌سازی ابزارک‌ها"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ابزارک‌ها در صفحه قفل"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"انتخاب ابزارک"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"برداشتن ابزارک"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"جای‌گذاری ابزارک انتخاب‌شده"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"تغییر کاربر"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"منوی پایین‌پر"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"همه برنامه‌ها و داده‌های این جلسه حذف خواهد شد."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"تنظیمات خروجی را وارد کنید"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"لغزنده‌های صدا ازهم باز شدند"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"لغزنده‌های صدا جمع شدند"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"‏بی‌صدا کردن %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"‏باصدا کردن %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"درحال پخش <xliff:g id="LABEL">%s</xliff:g> در"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"صدا در این دستگاه پخش می‌شود:"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"تماس برقرار است"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"تنظیم‌کننده واسط کاربری سیستم"</string>
     <string name="status_bar" msgid="4357390266055077437">"نوار وضعیت"</string>
     <string name="demo_mode" msgid="263484519766901593">"حالت نمایشی واسط کاربری سیستم"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"ماهواره، اتصال ضعیف است"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ماهواره، اتصال خوب است"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ماهواره، اتصال دردسترس است"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"درخواست کمک ماهواره‌ای"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"نمایه کاری"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"برای بعضی افراد سرگرم‌کننده است اما نه برای همه"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"‏«تنظیم‌کننده واسط کاربری سیستم» روش‌های بیشتری برای تنظیم دقیق و سفارشی کردن واسط کاربری Android در اختیار شما قرار می‌دهد. ممکن است این ویژگی‌های آزمایشی تغییر کنند، خراب شوند یا در نسخه‌های آینده جود نداشته باشند. با احتیاط ادامه دهید."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"افزودن کاشی"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"انتقال به <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"افزودن به موقعیت <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"موقعیت نامعتبر است."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"موقعیت <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"کاشی اضافه شد"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"کاشی حذف شد"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 5928cf8..8910bf3 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Vasen reuna <xliff:g id="PERCENT">%1$d</xliff:g> prosenttia"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Oikea reuna <xliff:g id="PERCENT">%1$d</xliff:g> prosenttia"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Tallennettu työprofiiliin tässä sovelluksessa: <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Tallennettu yksityiseen profiiliin tässä sovelluksessa: <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> havaitsi tämän kuvakaappauksen."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> ja jotkin muut sovellukset havaitsivat tämän kuvakaappauksen."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Yksilöi widgetit pitkällä painalluksella"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Muokkaa widgettejä"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Käytöstä poistetun widgetin sovelluskuvake"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Muokkaa widgetiä"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Poista"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Lisää widget"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Sulje widgetit lukitusnäytöllä"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Muokkaa widgetejä"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgetit lukitusnäytöllä"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"valitse widget"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"poista widget"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"aseta valittu widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Vaihda käyttäjää"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"alasvetovalikko"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Kaikki sovellukset ja tämän istunnon tiedot poistetaan."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Lisää tuloasetukset"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Äänenvoimakkuuden liukusäätimet laajennettu"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Äänenvoimakkuuden liukusäätimet tiivistetty"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"mykistä: %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"poista mykistys: %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Toistetaan: <xliff:g id="LABEL">%s</xliff:g>"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audiota toistetaan laitteella"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Puhelu kesken:"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"Tilapalkki"</string>
     <string name="demo_mode" msgid="263484519766901593">"Käyttöliittymän esittelytila"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelliitti, huono yhteys"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelliitti, hyvä yhteys"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelliitti, yhteys saatavilla"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Työprofiili"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Ei sovellu kaikkien käyttöön"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner antaa lisämahdollisuuksia Android-käyttöliittymän muokkaamiseen. Nämä kokeelliset ominaisuudet voivat muuttua, lakata toimimasta tai kadota milloin tahansa. Jatka omalla vastuullasi."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Lisää kiekko"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Siirrä paikkaan <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Lisää paikkaan <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Virheellinen sijainti."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Paikka <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kiekko lisätty"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kiekko poistettu"</string>
@@ -923,7 +928,7 @@
     <string name="mobile_data_text_format" msgid="6806501540022589786">"<xliff:g id="ID_1">%1$s</xliff:g> – <xliff:g id="ID_2">%2$s</xliff:g>"</string>
     <string name="mobile_carrier_text_format" msgid="8912204177152950766">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="MOBILE_DATA_TYPE">%2$s</xliff:g>"</string>
     <string name="wifi_is_off" msgid="5389597396308001471">"Wi-Fi on pois päältä"</string>
-    <string name="bt_is_off" msgid="7436344904889461591">"Bluetooth ei ole käytössä"</string>
+    <string name="bt_is_off" msgid="7436344904889461591">"Bluetooth on pois päältä"</string>
     <string name="dnd_is_off" msgid="3185706903793094463">"Älä häiritse ‑tila on pois päältä"</string>
     <string name="dnd_is_on" msgid="7009368176361546279">"Älä häiritse ‑tila on käytössä"</string>
     <string name="qs_dnd_prompt_auto_rule" msgid="3535469468310002616">"Automaattinen sääntö otti käyttöön Älä häiritse ‑tilan (<xliff:g id="ID_1">%s</xliff:g>)."</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 8cb63ea..df5f59f 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Limite gauche : <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Limite droite : <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Enregistré dans <xliff:g id="APP">%1$s</xliff:g> dans le profil professionnel"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Enregistré dans <xliff:g id="APP">%1$s</xliff:g> dans le profil privé"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fichiers"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> a détecté cette capture d\'écran."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> et d\'autres applications ouvertes ont détecté cette capture d\'écran."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Maintenez le doigt pour personnaliser les widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personnaliser les widgets"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icône d\'application pour un widget désactivé"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Modifier le widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Retirer"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ajouter un widget"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Fermer les widgets sur l\'écran de verrouillage"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personnaliser les widgets"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets sur l\'écran de verrouillage"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"sélectionner le widget"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"retirer le widget"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"placer le widget sélectionné"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Changer d\'utilisateur"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu déroulant"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Entrer les paramètres de sortie"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Curseurs de volume développés"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Curseurs de volume réduits"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"Désactivez le son de %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"Réactivez le son de %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Lecture de <xliff:g id="LABEL">%s</xliff:g> sur"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Lecture audio sur"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Appel en cours"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"Barre d\'état"</string>
     <string name="demo_mode" msgid="263484519766901593">"Mode Démo de l\'interface système"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Connexion satellite faible"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Bonne connexion satellite"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Connexion satellite accessible"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS par satellite"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profil professionnel"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Divertissant pour certains, mais pas pour tous"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner vous propose de nouvelles manières d\'adapter et de personnaliser l\'interface utilisateur d\'Android. Ces fonctionnalités expérimentales peuvent être modifiées, cesser de fonctionner ou disparaître dans les versions futures. À utiliser avec prudence."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Ajouter la tuile"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Déplacer vers <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Ajouter à la position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position incorrecte."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tuile ajoutée"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tuile retirée"</string>
@@ -1012,10 +1017,10 @@
     <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Touchez pour afficher le bouton d\'accessibilité"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Le raccourci <xliff:g id="FEATURE_NAME">%s</xliff:g> a été retiré"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# raccourci retiré}one{# raccourci retiré}many{# de raccourcis retirés}other{# raccourcis retirés}}"</string>
-    <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Déplacer dans coin sup. gauche"</string>
-    <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Déplacer dans coin sup. droit"</string>
-    <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Déplacer dans coin inf. gauche"</string>
-    <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Déplacer dans coin inf. droit"</string>
+    <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Déplacer en haut à gauche"</string>
+    <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Déplacer en haut à droite"</string>
+    <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Déplacer en bas à gauche"</string>
+    <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Déplacer en bas à droite"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Rapprocher du bord et masquer"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Éloigner du bord et afficher"</string>
     <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Retirer"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index fd11a11..b02d547 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Limite gauche : <xliff:g id="PERCENT">%1$d</xliff:g> pour cent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Limite droite : <xliff:g id="PERCENT">%1$d</xliff:g> pour cent"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Enregistré dans <xliff:g id="APP">%1$s</xliff:g>, dans le profil professionnel"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Enregistré dans <xliff:g id="APP">%1$s</xliff:g> dans le profil privé"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fichiers"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> a détecté cette capture d\'écran."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> et d\'autres applis ouvertes ont détecté cette capture d\'écran."</string>
@@ -267,7 +266,7 @@
     <string name="quick_settings_dnd_label" msgid="7728690179108024338">"Ne pas déranger"</string>
     <string name="quick_settings_bluetooth_label" msgid="7018763367142041481">"Bluetooth"</string>
     <string name="quick_settings_bluetooth_detail_empty_text" msgid="5760239584390514322">"Aucun appareil associé disponible."</string>
-    <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Appuyer pour connecter ou déconnecter un appareil"</string>
+    <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Appuyez pour connecter ou déconnecter un appareil"</string>
     <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Associer un nouvel appareil"</string>
     <string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Tout afficher"</string>
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Utiliser le Bluetooth"</string>
@@ -369,7 +368,7 @@
     <string name="quick_settings_contrast_standard" msgid="2538227821968061832">"Standard"</string>
     <string name="quick_settings_contrast_medium" msgid="5158352575583902566">"Moyen"</string>
     <string name="quick_settings_contrast_high" msgid="656049259587494499">"Élevé"</string>
-    <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"Prothèses auditives"</string>
+    <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"Appareils auditifs"</string>
     <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"Appareils auditifs"</string>
     <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Associer un nouvel appareil"</string>
     <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Cliquer pour associer un nouvel appareil"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Appuyez de manière prolongée pour personnaliser les widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personnaliser les widgets"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icône d\'appli du widget désactivé"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Modifier le widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Supprimer"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ajouter un widget"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Fermer les widgets sur l\'écran de verrouillage"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personnaliser les widgets"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets sur l\'écran de verrouillage"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"sélectionner un widget"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"supprimer le widget"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"positionner le widget sélectionné"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Changer d\'utilisateur"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu déroulant"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Accéder aux paramètres de sortie"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Curseurs de volume développés"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Curseurs de volume réduits"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"couper le son de %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"réactiver le son de %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Diffusion de <xliff:g id="LABEL">%s</xliff:g> sur"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Lecture audio sur"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Appel défini sur"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"Barre d\'état"</string>
     <string name="demo_mode" msgid="263484519766901593">"Mode démo de l\'UI du système"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Mauvaise connexion satellite"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Bonne connexion satellite"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Connexion satellite disponible"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS par satellite"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profil professionnel"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Divertissant pour certains, mais pas pour tous"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner vous propose de nouvelles manières d\'adapter et de personnaliser l\'interface utilisateur Android. Ces fonctionnalités expérimentales peuvent être modifiées, cesser de fonctionner ou disparaître dans les versions futures. À utiliser avec prudence."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Ajouter un bloc"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Déplacer vers <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Ajouter à la position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Emplacement non valide."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Bloc ajouté"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Bloc supprimé"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 5589e3a..8e2a011 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Bordo esquerdo: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Bordo dereito: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Captura de pantalla gardada na aplicación <xliff:g id="APP">%1$s</xliff:g> do perfil de traballo"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Gardouse na aplicación <xliff:g id="APP">%1$s</xliff:g> do perfil privado"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Ficheiros"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> detectou esta captura de pantalla."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> e outras aplicacións abertas detectaron esta captura de pantalla."</string>
@@ -371,7 +370,7 @@
     <string name="quick_settings_contrast_high" msgid="656049259587494499">"Nivel alto"</string>
     <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"Dispositivos auditivos"</string>
     <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"Dispositivos auditivos"</string>
-    <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Vincular un dispositivo novo"</string>
+    <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Vincular dispositivo novo"</string>
     <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Fai clic para vincular un novo dispositivo"</string>
     <string name="hearing_devices_presets_error" msgid="350363093458408536">"Non se puido actualizar a configuración predeterminada"</string>
     <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Queres desbloquear o micrófono do dispositivo?"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pulsación longa para personalizar os widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icona da aplicación de widget desactivado"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Quitar"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Engadir widget"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Ocultar os widgets na pantalla de bloqueo"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizar os widgets"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets na pantalla de bloqueo"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"seleccionar widget"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"quitar o widget"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"colocar o widget seleccionado"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar usuario"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú despregable"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Eliminaranse todas as aplicacións e datos desta sesión."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Introducir a configuración de saída"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Controis desprazables de volume despregados"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Controis desprazables de volume contraídos"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"silenciar %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"activar o son de %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Reproducindo <xliff:g id="LABEL">%s</xliff:g> en"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Reproducirase en"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Chamada en curso"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Configurador da IU do sistema"</string>
     <string name="status_bar" msgid="4357390266055077437">"Barra de estado"</string>
     <string name="demo_mode" msgid="263484519766901593">"Modo de demostración da IU do sistema"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satélite, mala conexión"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, boa conexión"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, conexión dispoñible"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS por satélite"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Perfil de traballo"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Diversión só para algúns"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"O configurador da IU do sistema ofréceche formas adicionais de modificar e personalizar a interface de usuario de Android. Estas funcións experimentais poden cambiar, interromperse ou desaparecer en futuras versións. Continúa con precaución."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Engadir tarxeta"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mover a <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Engadir á posición <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posición non válida."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posición <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Engadiuse a tarxeta"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Quitouse a tarxeta"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 58b4f9b..c0f6be9 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ડાબી બાજુની સીમા <xliff:g id="PERCENT">%1$d</xliff:g> ટકા"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"જમણી બાજુની સીમા <xliff:g id="PERCENT">%1$d</xliff:g> ટકા"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"ઑફિસની પ્રોફાઇલમાં <xliff:g id="APP">%1$s</xliff:g>માં સાચવવામાં આવ્યો"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"<xliff:g id="APP">%1$s</xliff:g>માં ખાનગી પ્રોફાઇલમાં સાચવવામાં આવ્યો"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ફાઇલો"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> દ્વારા આ સ્ક્રીનશૉટ લેવાયાની ભાળ મેળવવામાં આવી."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> અને કામ કરતી અન્ય ઍપ દ્વારા આ સ્ક્રીનશૉટ લેવાયાની ભાળ મેળવવામાં આવી."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"વિજેટ કસ્ટમાઇઝ કરવા માટે થોડીવાર દબાવી રાખો"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"વિજેટ કસ્ટમાઇઝ કરો"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"બંધ કરેલા વિજેટ માટેની ઍપનું આઇકન"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"વિજેટમાં ફેરફાર કરો"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"કાઢી નાખો"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"વિજેટ ઉમેરો"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"લૉક સ્ક્રીન પર વિજેટ બંધ કરો"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"વિજેટ કસ્ટમાઇઝ કરો"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"લૉક સ્ક્રીન પર વિજેટ"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"વિજેટ પસંદ કરો"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"વિજેટ કાઢી નાખો"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"પસંદ કરેલું વિજેટ મૂકો"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"વપરાશકર્તા સ્વિચ કરો"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"પુલડાઉન મેનૂ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"આ સત્રમાંની તમામ ઍપ અને ડેટા કાઢી નાખવામાં આવશે."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"આઉટપુટના સેટિંગ દાખલ કરો"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"વૉલ્યૂમના સ્લાઇડર મોટા કરવામાં આવ્યા"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"વૉલ્યૂમના સ્લાઇડર નાના કરવામાં આવ્યા"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%sને મ્યૂટ કરો"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%sને અનમ્યૂટ કરો"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> વગાડી રહ્યાં છીએ"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"ઑડિયો આની પર વાગશે"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"કૉલ ચાલુ છે"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"સિસ્ટમ UI ટ્યૂનર"</string>
     <string name="status_bar" msgid="4357390266055077437">"સ્ટેટસ બાર"</string>
     <string name="demo_mode" msgid="263484519766901593">"સિસ્ટમ UI ડેમો મોડ"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"સૅટલાઇટ, નબળું કનેક્શન"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"સૅટલાઇટ, સારું કનેક્શન"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"સૅટલાઇટ, કનેક્શન ઉપલબ્ધ છે"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ઇમર્જન્સી સૅટલાઇટ સહાય"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"ઑફિસની પ્રોફાઇલ"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"કેટલાક માટે મજા પરંતુ બધા માટે નહીં"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"સિસ્ટમ UI ટ્યૂનર તમને Android વપરાશકર્તા ઇન્ટરફેસને ટ્વીક અને કસ્ટમાઇઝ કરવાની વધારાની રીતો આપે છે. ભાવિ રીલિઝેસમાં આ પ્રાયોગિક સુવિધાઓ બદલાઈ, ભંગ અથવા અદૃશ્ય થઈ શકે છે. સાવધાની સાથે આગળ વધો."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"ટાઇલ ઉમેરો"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> પર ખસેડો"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"જગ્યા પર <xliff:g id="POSITION">%1$d</xliff:g> ઉમેરો"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"સ્થિતિ અમાન્ય છે."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"જગ્યા <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ટાઇલ ઉમેરી"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ટાઇલ કાઢી નાખી"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 681ead9..4cf74b5 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"बाएं किनारे से <xliff:g id="PERCENT">%1$d</xliff:g> प्रतिशत"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"दाएं किनारे से <xliff:g id="PERCENT">%1$d</xliff:g> प्रतिशत"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"वर्क प्रोफ़ाइल में मौजूद <xliff:g id="APP">%1$s</xliff:g> में सेव किया गया है"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"स्क्रीनशॉट को <xliff:g id="APP">%1$s</xliff:g> की निजी प्रोफ़ाइल में सेव किया गया"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> को इस स्क्रीनशॉट का पता चला है."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> और खुले हुए अन्य ऐप्लिकेशन को इस स्क्रीनशॉट का पता चला है."</string>
@@ -276,7 +275,7 @@
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"सेव किया गया"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"डिसकनेक्ट करें"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"चालू करें"</string>
-    <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"कल फिर से अपने-आप चालू हो जाएगा"</string>
+    <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"कल फिर से अपने-आप चालू हो जाए"</string>
     <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"क्विक शेयर और Find My Device जैसी सुविधाएं, ब्लूटूथ का इस्तेमाल करती हैं"</string>
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"ब्लूटूथ कल सुबह चालू होगा"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"ऑडियो शेयर करने की सुविधा"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"विजेट पसंद के मुताबिक बनाने के लिए उसे दबाकर रखें"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"विजेट अपनी पसंद के मुताबिक बनाएं"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"बंद किए गए विजेट के लिए ऐप्लिकेशन आइकॉन"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"विजेट में बदलाव करें"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"हटाएं"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"विजेट जोड़ें"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"लॉक स्क्रीन पर विजेट बंद करें"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"विजेट अपनी पसंद के मुताबिक बनाएं"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"लॉक स्क्रीन पर विजेट"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"विजेट चुनें"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"विजेट हटाएं"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"चुने गए विजेट के लिए जगह चुनें"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"उपयोगकर्ता बदलें"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेन्यू"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"इस सेशन के सभी ऐप्लिकेशन और डेटा को हटा दिया जाएगा."</string>
@@ -560,7 +562,7 @@
     <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"इस डिवाइस का प्रबंधन आपके अभिभावक करते हैं. अभिभावक आपके डिवाइस से जुड़ी जानकारी देख सकते हैं. साथ ही, इसे प्रबंधित कर सकते हैं. इनमें आपके इस्तेमाल किए गए ऐप्लिकेशन, जगह की जानकारी, और डिवाइस के इस्तेमाल में बिताए गए समय जैसी जानकारी शामिल है."</string>
     <string name="legacy_vpn_name" msgid="4174223520162559145">"वीपीएन"</string>
     <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"TrustAgent की वजह से अनलॉक रखा गया है"</string>
-    <string name="kg_prompt_after_adaptive_auth_lock" msgid="2587481497846342760">"कई बार पुष्टि करने की कोशिश की वजह से, डिवाइस लॉक है"</string>
+    <string name="kg_prompt_after_adaptive_auth_lock" msgid="2587481497846342760">"कई बार पुष्टि करने की कोशिशों की वजह से, डिवाइस लॉक हो गया है"</string>
     <string name="keyguard_indication_after_adaptive_auth_lock" msgid="2323400645470712787">"डिवाइस लॉक हो गया है\nपुष्टि नहीं की जा सकी"</string>
     <string name="zen_mode_and_condition" msgid="5043165189511223718">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
     <string name="accessibility_volume_settings" msgid="1458961116951564784">"साउंड सेटिंग"</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"आउटपुट की सेटिंग डालें"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"आवाज़ के स्लाइडर को बड़ा किया गया"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"आवाज़ के स्लाइडर को छोटा किया गया"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s को म्यूट करें"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s को अनम्यूट करें"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> इस पर चल रहा है"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"ऑडियो इस पर चलेगा"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"कॉल चालू है"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"सिस्टम यूज़र इंटरफ़ेस (यूआई) ट्यूनर"</string>
     <string name="status_bar" msgid="4357390266055077437">"स्टेटस बार"</string>
     <string name="demo_mode" msgid="263484519766901593">"सिस्टम यूज़र इंटरफ़ेस (यूआई) डेमो मोड"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"सैटलाइट कनेक्शन खराब है"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"सैटलाइट कनेक्शन अच्छा है"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"सैटलाइट कनेक्शन उपलब्ध है"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"सैटलाइट एसओएस"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"वर्क प्रोफ़ाइल"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"कुछ के लिए मज़ेदार लेकिन सबके लिए नहीं"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"सिस्टम यूज़र इंटरफ़ेस (यूआई) ट्यूनर, आपको Android यूज़र इंटरफ़ेस में सुधार लाने और उसे अपनी पसंद के हिसाब से बदलने के कुछ और तरीके देता है. प्रयोग के तौर पर इस्तेमाल हो रहीं ये सुविधाएं आगे चल कर रिलीज़ की जा सकती हैं, रोकी जा सकती हैं या दिखाई देना बंद हो सकती हैं. सावधानी से आगे बढ़ें."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"टाइल जोड़ें"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"टाइल को <xliff:g id="POSITION">%1$d</xliff:g> पोज़िशन पर ले जाएं"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"टाइल को <xliff:g id="POSITION">%1$d</xliff:g> पोज़िशन पर जोड़ें"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"मौजूदा जगह अमान्य है."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"टाइल की पोज़िशन <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"टाइल जोड़ी गई"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"टाइल हटाई गई"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index a557036..b211c94 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Lijevi rub <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Desni rub <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Spremljeno u aplikaciju <xliff:g id="APP">%1$s</xliff:g> u poslovnom profilu"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Spremljeno u aplikaciju <xliff:g id="APP">%1$s</xliff:g> na privatnom profilu"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Datoteke"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"Aplikacija <xliff:g id="APPNAME">%1$s</xliff:g> otkrila je ovu snimku zaslona."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> i druge otvorene aplikacije otkrile su ovu snimku zaslona."</string>
@@ -370,8 +369,8 @@
     <string name="quick_settings_contrast_medium" msgid="5158352575583902566">"Srednji"</string>
     <string name="quick_settings_contrast_high" msgid="656049259587494499">"Visoki"</string>
     <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"Slušna pomagala"</string>
-    <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"Slušni uređaji"</string>
-    <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Uparivanje novog uređaja"</string>
+    <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"Slušna pomagala"</string>
+    <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Uparite novi uređaj"</string>
     <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Kliknite da biste uparili novi uređaj"</string>
     <string name="hearing_devices_presets_error" msgid="350363093458408536">"Ažuriranje unaprijed definiranih postavki nije uspjelo"</string>
     <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Želite li deblokirati mikrofon uređaja?"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Dugo pritisnite za prilagodbu widgeta"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prilagodi widgete"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacije za onemogućeni widget"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Uredi widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Ukloni"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodaj widget"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Zatvaranje widgeta na zaključanom zaslonu"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Prilagodi widgete"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgeti na zaključanom zaslonu"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"odaberi widget"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ukloni widget"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"postavi odabrani widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Promjena korisnika"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući izbornik"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Izbrisat će se sve aplikacije i podaci u ovoj sesiji."</string>
@@ -592,9 +594,9 @@
     <string name="screen_pinning_negative" msgid="6882816864569211666">"Ne, hvala"</string>
     <string name="screen_pinning_start" msgid="7483998671383371313">"Aplikacija je prikvačena"</string>
     <string name="screen_pinning_exit" msgid="4553787518387346893">"Aplikacija je otkvačena"</string>
-    <string name="stream_voice_call" msgid="7468348170702375660">"Nazovi"</string>
+    <string name="stream_voice_call" msgid="7468348170702375660">"Poziv"</string>
     <string name="stream_system" msgid="7663148785370565134">"Sustav"</string>
-    <string name="stream_ring" msgid="7550670036738697526">"Zvoni"</string>
+    <string name="stream_ring" msgid="7550670036738697526">"Zvonjenje"</string>
     <string name="stream_music" msgid="2188224742361847580">"Mediji"</string>
     <string name="stream_alarm" msgid="16058075093011694">"Alarm"</string>
     <string name="stream_notification" msgid="7930294049046243939">"Obavijest"</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Unesite postavke izlaza"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Proširivanje klizača za glasnoću"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Sažimanje klizača za glasnoću"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"isključili zvuk za sljedeće: %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"uključili zvuk za sljedeće: %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> se reproducira na"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Zvuk će se reproducirati na"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Pozivanje na uređaju"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Ugađanje korisničkog sučelja sustava"</string>
     <string name="status_bar" msgid="4357390266055077437">"Traka statusa"</string>
     <string name="demo_mode" msgid="263484519766901593">"Demo način korisničkog sučelja sustava"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelit, slaba veza"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, dobra veza"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, veza je dostupna"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS putem satelita"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Poslovni profil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Zabava za neke, ali ne za sve"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Ugađanje korisničkog sučelja sustava pruža vam dodatne načine za prilagodbu korisničkog sučelja Androida. Te se eksperimentalne značajke mogu promijeniti, prekinuti ili nestati u budućim izdanjima. Nastavite uz oprez."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Dodavanje kartice"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Premještanje u prostoriju <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodavanje na položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Položaj nije važeći."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kartica je dodana"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kartica je uklonjena"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 06c9ff6..e736537 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Bal oldali rész <xliff:g id="PERCENT">%1$d</xliff:g> százaléka"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Jobb oldali rész <xliff:g id="PERCENT">%1$d</xliff:g> százaléka"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Mentve a(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazás munkaprofiljába"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Mentve a(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazás privát profiljában"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fájlok"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"A(z) <xliff:g id="APPNAME">%1$s</xliff:g> észlelte ezt a képernyőképet."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"A(z) <xliff:g id="APPNAME">%1$s</xliff:g> és más nyitott alkalmazások észlelték ezt a képernyőképet."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Nyomja meg hosszan a modulok személyre szabásához"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Modulok személyre szabása"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Letiltott modul alkalmazásikonja"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Modul szerkesztése"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Eltávolítás"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Modul hozzáadása"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"A lezárási képernyőn lévő modulok bezárása"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Modulok személyre szabása"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Modulok a lezárási képernyőn"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"modul kiválasztása"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"modul törlése"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"kijelölt modul áthelyezése"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Felhasználóváltás"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"lehúzható menü"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"A munkamenetben található összes alkalmazás és adat törlődni fog."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Kimenet beállításainak megadása"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Hangerő-szabályozók kibontva"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Hangerő-szabályozók összecsukva"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s némítása"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s némításának feloldása"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> lejátszása itt:"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Hang lejátszása itt:"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Hívás folyamatban itt:"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Kezelőfelület-hangoló"</string>
     <string name="status_bar" msgid="4357390266055077437">"Állapotsor"</string>
     <string name="demo_mode" msgid="263484519766901593">"A rendszer kezelőfelületének demómódja"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Műhold, gyenge kapcsolat"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Műhold, jó kapcsolat"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Műhold, van rendelkezésre álló kapcsolat"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Műholdas SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Munkaprofil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Egyeseknek tetszik, másoknak nem"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"A Kezelőfelület-hangoló az Android felhasználói felületének szerkesztéséhez és testreszabásához nyújt további megoldásokat. Ezek a kísérleti funkciók változhatnak vagy megsérülhetnek a későbbi kiadásokban, illetve eltűnhetnek azokból. Körültekintően járjon el."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Mozaik hozzáadása"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Áthelyezés ide: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Hozzáadás a következő pozícióhoz: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Érvénytelen pozíció."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>. hely"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kártya hozzáadva"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kártya eltávolítva"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index ff2f4df..6a905c5 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Ձախ կողմի սահմանագիծը՝ <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Աջ կողմի սահմանագիծը՝ <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Սքրինշոթը պահվեց <xliff:g id="APP">%1$s</xliff:g>-ի աշխատանքային պրոֆիլում"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Սքրինշոթը պահվեց <xliff:g id="APP">%1$s</xliff:g>-ի անձնական պրոֆիլում"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Ֆայլեր"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> հավելվածը հայտնաբերել է այս սքրինշոթը։"</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g>-ն ու բացված այլ հավելվածներ հայտնաբերել են այս սքրինշոթը։"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Երկար սեղմեք՝ վիջեթները հարմարեցնելու համար"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Հարմարեցնել վիջեթները"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Հավելվածի պատկերակ անջատված վիջեթի համար"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Փոփոխել վիջեթը"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Հեռացնել"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ավելացնել վիջեթ"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Փակել վիջեթները կողպէկրանին"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Հարմարեցնել վիջեթները"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Վիջեթներ կողպէկրանին"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ընտրել վիջեթ"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"հեռացնել վիջեթը"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"տեղադրել ընտրված վիջեթը"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Անջատել օգտվողին"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"իջնող ընտրացանկ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Այս աշխատաշրջանի բոլոր հավելվածներն ու տվյալները կջնջվեն:"</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Բացել նվագարկման կարգավորումները"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Ձայնի ուժգնության սահիչները ծավալված են"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Ձայնի ուժգնության սահիչները ծալված են"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"անջատել ձայնը (%s)"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"միացնել ձայնը (%s)"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g>. նվագարկվում է"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Աուդիոն կնվագարկի"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Զանգն ընթացքում է"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Համակարգի ՕՄ-ի կարգավորիչ"</string>
     <string name="status_bar" msgid="4357390266055077437">"Կարգավիճակի գոտի"</string>
     <string name="demo_mode" msgid="263484519766901593">"Համակարգի միջերեսի ցուցադրական ռեժիմ"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Արբանյակային թույլ կապ"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Արբանյակային լավ կապ"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Հասանելի է արբանյակային կապ"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Աշխատանքային պրոֆիլ"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Զվարճանք մեկ՝ որոշակի մարդու համար"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Համակարգի ՕՄ-ի կարգավորիչը հնարավորություն է տալիս հարմարեցնել Android-ի օգտատիրոջ միջերեսը: Այս փորձնական գործառույթները կարող են հետագա թողարկումների մեջ փոփոխվել, խափանվել կամ ընդհանրապես չհայտնվել: Եթե շարունակում եք, զգուշացեք:"</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Ավելացնել սալիկ"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Տեղափոխել դիրք <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Ավելացնել դիրք <xliff:g id="POSITION">%1$d</xliff:g>-ում"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Դիրքն անվավեր է։"</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Դիրք <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Սալիկն ավելացվեց"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Սալիկը հեռացվեց"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 1aa3d9f..23d8397 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Batas kiri <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Batas kanan <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Disimpan di <xliff:g id="APP">%1$s</xliff:g> di profil kerja"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Disimpan di <xliff:g id="APP">%1$s</xliff:g> di profil pribadi"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"File"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> mendeteksi screenshot ini."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> dan aplikasi terbuka lainnya mendeteksi screenshot ini."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Tekan lama untuk menyesuaikan widget"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Sesuaikan widget"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikon aplikasi untuk widget yang dinonaktifkan"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Hapus"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Tambahkan widget"</string>
@@ -461,7 +462,10 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Tutup widget di layar kunci"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Sesuaikan widget"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widget di layar kunci"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"pilih widget"</string>
+    <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) -->
+    <skip />
+    <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) -->
     <skip />
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Beralih pengguna"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu pulldown"</string>
@@ -627,12 +631,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Masukkan setelan perangkat output"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Penggeser volume diluaskan"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Penggeser volume diciutkan"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"membisukan %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"membunyikan %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Memutar <xliff:g id="LABEL">%s</xliff:g> di"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio akan diputar di"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Menelepon di"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Penyetel Antarmuka Pengguna Sistem"</string>
     <string name="status_bar" msgid="4357390266055077437">"Bilah status"</string>
     <string name="demo_mode" msgid="263484519766901593">"Mode demo UI sistem"</string>
@@ -660,8 +669,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelit, koneksi buruk"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, koneksi baik"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, koneksi tersedia"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS via Satelit"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profil kerja"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Tidak semua orang menganggapnya baik"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Penyetel Antarmuka Pengguna Sistem memberikan cara tambahan untuk mengubah dan menyesuaikan antarmuka pengguna Android. Fitur eksperimental ini dapat berubah, rusak, atau menghilang dalam rilis di masa mendatang. Lanjutkan dengan hati-hati."</string>
@@ -866,8 +874,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Tambahkan kartu"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Pindahkan ke <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Tambahkan ke posisi <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posisi tidak valid."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posisi <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kartu ditambahkan"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kartu dihapus"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 6ba831b..2c77eb1 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -447,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Haltu inni til að sérsníða græjur"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Sérsníða græjur"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Forritstákn fyrir græju sem slökkt er á"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Breyta græju"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Fjarlægja"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Bæta græju við"</string>
@@ -461,6 +463,8 @@
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Sérsníða græjur"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Græjur á lásskjá"</string>
     <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"velja græju"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"fjarlægja græju"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"koma valinni græju fyrir"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Skipta um notanda"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"Fellivalmynd"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Öllum forritum og gögnum í þessari lotu verður eytt."</string>
@@ -625,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Færa inn stillingar úttaks"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Stækkaðir hljóðstyrkssleðar"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Minnkaðir hljóðstyrkssleðar"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"þagga %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"kveikja á hljóði %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Í spilun í <xliff:g id="LABEL">%s</xliff:g>"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Hljóð heldur áfram að spilast"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Símtal í gangi"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Fínstillingar kerfisviðmóts"</string>
     <string name="status_bar" msgid="4357390266055077437">"Stöðustika"</string>
     <string name="demo_mode" msgid="263484519766901593">"Prufustilling kerfisviðmóts"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 5825be4..056c286 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Limite sinistro, <xliff:g id="PERCENT">%1$d</xliff:g> percento"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Limite destro, <xliff:g id="PERCENT">%1$d</xliff:g> percento"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Salvato nell\'app <xliff:g id="APP">%1$s</xliff:g> nel profilo di lavoro"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Salvato nel profilo privato nell\'app <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"File"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ha rilevato questo screenshot."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> e altre app aperte hanno rilevato questo screenshot."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Premi a lungo per personalizzare i widget"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizza widget"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icona dell\'app per widget disattivati"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Modifica widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Rimuovi"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Aggiungi widget"</string>
@@ -461,7 +462,10 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Chiudi widget su schermata di blocco"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizza widget"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widget su schermata di blocco"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"seleziona widget"</string>
+    <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) -->
+    <skip />
+    <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) -->
     <skip />
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambio utente"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu a discesa"</string>
@@ -627,12 +631,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Inserisci impostazioni di uscita"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Cursori volume espansi"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Cursori volume compressi"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"disattivare audio di %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"riattivare audio di %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> in riproduzione su"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio riprodotto su:"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Chiamata in corso"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Ottimizzatore UI di sistema"</string>
     <string name="status_bar" msgid="4357390266055077437">"Barra di stato"</string>
     <string name="demo_mode" msgid="263484519766901593">"Modalità demo dell\'interfaccia utente di sistema"</string>
@@ -660,8 +669,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satellitare, connessione debole"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellitare, connessione buona"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellitare, connessione disponibile"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS satellitare"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profilo di lavoro"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Il divertimento riservato a pochi eletti"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"L\'Ottimizzatore UI di sistema mette a disposizione altri metodi per modificare e personalizzare l\'interfaccia utente di Android. Queste funzioni sperimentali potrebbero cambiare, interrompersi o scomparire nelle versioni successive. Procedi con cautela."</string>
@@ -866,8 +874,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Aggiungi riquadro"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Sposta nella posizione <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Aggiungi alla posizione <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posizione non valida."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posizione <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Riquadro aggiunto"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Riquadro rimosso"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 33b5db0..6670b09 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"<xliff:g id="PERCENT">%1$d</xliff:g> אחוז מהשוליים השמאליים"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"<xliff:g id="PERCENT">%1$d</xliff:g> אחוז מהשוליים הימניים"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"נשמר באפליקציה <xliff:g id="APP">%1$s</xliff:g> בתוך פרופיל העבודה"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"נשמר באפליקציה <xliff:g id="APP">%1$s</xliff:g> בפרופיל הפרטי"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"קבצים"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"האפליקציה <xliff:g id="APPNAME">%1$s</xliff:g> זיהתה את צילום המסך הזה."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"האפליקציה <xliff:g id="APPNAME">%1$s</xliff:g> ואפליקציות פתוחות נוספות זיהו את צילום המסך הזה."</string>
@@ -270,7 +269,7 @@
     <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"אפשר להקיש כדי להתחבר למכשיר או להתנתק ממנו"</string>
     <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"התאמה של מכשיר חדש"</string>
     <string name="see_all_bluetooth_devices" msgid="1761596816620200433">"הצגת הכול"</string>
-    <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth"</string>
+    <string name="turn_on_bluetooth" msgid="5681370462180289071">"‏שימוש ב-Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"מחובר"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"שיתוף אודיו"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"נשמר"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"לוחצים לחיצה ארוכה כדי להתאים אישית את הווידג\'טים"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"התאמה אישית של ווידג\'טים"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"סמל האפליקציה לווידג\'ט שהושבת"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"עריכת הווידג\'ט"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"הסרה"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"הוספת ווידג\'ט"</string>
@@ -461,7 +462,10 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"סגירת ווידג\'טים במסך הנעילה"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"התאמה אישית של ווידג\'טים"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ווידג\'טים במסך הנעילה"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"צריך לבחור ווידג\'ט"</string>
+    <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) -->
+    <skip />
+    <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) -->
     <skip />
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"החלפת משתמש"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"תפריט במשיכה למטה"</string>
@@ -627,12 +631,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"הזנה של הגדרות הפלט"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"פסי ההזזה של עוצמת הקול במצב מורחב"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"פסי ההזזה של עוצמת הקול במצב מכווץ"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"‏השתקה של %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"‏ביטול ההשתקה של %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"הפעלה של <xliff:g id="LABEL">%s</xliff:g> במכשיר"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"האודיו יופעל במכשיר"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"מתבצעת שיחה במכשיר"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"שורת סטטוס"</string>
     <string name="demo_mode" msgid="263484519766901593">"מצב הדגמה בממשק המשתמש של המערכת"</string>
@@ -660,8 +669,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"לוויין, חיבור באיכות ירודה"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"לוויין, חיבור באיכות טובה"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"לוויין, יש חיבור זמין"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"תקשורת לוויינית למצב חירום"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"פרופיל עבודה"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"מהנה בשביל חלק מהאנשים, אבל לא בשביל כולם"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"‏התכונה System UI Tuner מספקת לך דרכים נוספות להתאים אישית את ממשק המשתמש של Android. התכונות הניסיוניות האלה עשויות להשתנות, לא לעבוד כראוי או להיעלם בגרסאות עתידיות. יש להמשיך בזהירות."</string>
@@ -866,8 +874,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"הוספת לחצן"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"העברה למיקום <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"הוספה למיקום <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"המיקום לא תקין."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"מיקום <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"הלחצן נוסף"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"הלחצן הוסר"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index ac8d81c..e3c0dbc 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -272,7 +272,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth を使用"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"接続しました"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"音声の共有"</string>
-    <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"保存しました"</string>
+    <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"保存済み"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"接続を解除"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"有効化"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"明日自動的に ON に戻す"</string>
@@ -447,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"長押ししてウィジェットをカスタマイズ"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ウィジェットのカスタマイズ"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"無効なウィジェットのアプリアイコン"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"ウィジェットを編集"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"削除"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ウィジェットを追加"</string>
@@ -461,6 +463,8 @@
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ウィジェットのカスタマイズ"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ロック画面のウィジェット"</string>
     <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ウィジェットを選択"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ウィジェットを削除"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"選択したウィジェットを配置"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ユーザーを切り替える"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"プルダウン メニュー"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"このセッションでのアプリとデータはすべて削除されます。"</string>
@@ -625,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"出力の設定を入力してください"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"音量スライダーを開きました"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"音量スライダーを閉じました"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s をミュート"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s のミュートを解除"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> の再生先:"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"音声の再生先"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"通話中"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"システムUI調整ツール"</string>
     <string name="status_bar" msgid="4357390266055077437">"ステータスバー"</string>
     <string name="demo_mode" msgid="263484519766901593">"システム UI デモモード"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 7b7ee68..0251ac0 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"მარცხენა ზღვარი: <xliff:g id="PERCENT">%1$d</xliff:g> პროცენტი"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"მარჯვენა ზღვარი: <xliff:g id="PERCENT">%1$d</xliff:g> პროცენტი"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"შენახულია <xliff:g id="APP">%1$s</xliff:g>-ში სამსახურის პროფილში"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"შენახულია <xliff:g id="APP">%1$s</xliff:g>-ში, პირად პროფილში"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ფაილები"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g>-მა აღმოაჩინა ეკრანის ეს ანაბეჭდი"</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g>-მა და სხვა გახსნილმა აპებმა აღმოაჩინეს ეკრანის ეს ანაბეჭდი."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ხანგრძლივად დააჭირეთ ვიჯეტების მოსარგებად"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ვიჯეტების მორგება"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"აპის ხატულა გათიშული ვიჯეტისთვის"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"ვიჯეტის რედაქტირება"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ამოშლა"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ვიჯეტის დამატება"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"ჩაკეტილ ეკრანზე ვიჯეტების დახურვა"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ვიჯეტების მორგება"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ვიჯეტები ჩაკეტილ ეკრანზე"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ვიჯეტის არჩევა"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ვიჯეტის ამოშლა"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"არჩეული ვიჯეტის განთავსება"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"მომხმარებლის გადართვა"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ჩამოშლადი მენიუ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ამ სესიის ყველა აპი და მონაცემი წაიშლება."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"აუდიოს გამოსვლის პარამეტრების გახსნა"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ხმის სლაიდერების გაფართოება"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ხმის სლაიდერების ჩაკეცვა"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s-ის დადუმება"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s-ის დადუმების მოხსნა"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"უკრავს <xliff:g id="LABEL">%s</xliff:g>:"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"აუდიო დაიკვრება"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"მიმდინარეობს ზარი"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"სისტემის UI ტუნერი"</string>
     <string name="status_bar" msgid="4357390266055077437">"სტატუსის ზოლი"</string>
     <string name="demo_mode" msgid="263484519766901593">"სისტემის UI-ს დემო-რეჟიმი"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"სუსტი სატელიტური კავშირი"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"კარგი სატელიტური კავშირი"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ხელმისაწვდომია სატელიტური კავშირი"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"სატელიტური SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"სამსახურის პროფილი"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"ზოგისთვის გასართობია, მაგრამ არა ყველასთვის"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"სისტემის UI ტუნერი გაძლევთ დამატებით გზებს Android-ის სამომხმარებლო ინტერფეისის პარამეტრების დაყენებისთვის. ეს ექსპერიმენტული მახასიათებლები შეიძლება შეიცვალოს, შეწყდეს ან გაქრეს მომავალ ვერსიებში. სიფრთხილით გააგრძელეთ."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"მოზაიკის დამატება"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"გადატანა <xliff:g id="POSITION">%1$d</xliff:g>-ზე"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"დამატება პოზიციაზე <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"პოზიცია არასწორია."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"პოზიცია <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"მოზაიკის ფილა დაემატა"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"მოზაიკის ფილა ამოიშალა"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index a3ba3d7..9c96a4e 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Сол жақ шектік сызық: <xliff:g id="PERCENT">%1$d</xliff:g> пайыз"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Оң жақ шектік сызық: <xliff:g id="PERCENT">%1$d</xliff:g> пайыз"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Жұмыс профиліндегі <xliff:g id="APP">%1$s</xliff:g> қолданбасында сақталған."</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Жеке профильдегі <xliff:g id="APP">%1$s</xliff:g> қолданбасында сақталды."</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> қолданбасы осы скриншотты анықтады."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> және басқа да ашық қолданбалар осы скриншотты анықтады."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Виджеттерді бейімдеу үшін ұзақ басып тұрыңыз."</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Виджеттерді реттеу"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Өшірілген виджеттің қолданба белгішесі"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Виджетті өзгерту"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Өшіру"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Виджет қосу"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Құлыптаулы экранда виджеттерді жабу"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Виджеттерді бейімдеу"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Құлыптаулы экрандағы виджеттер"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"виджет таңдау"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"виджетті өшіру"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"таңдалған виджетті орналастыру"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Пайдаланушыны ауыстыру"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ашылмалы мәзір"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Осы сеанстағы барлық қолданба мен дерек жойылады."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Шығыс параметрлерін енгізу"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Дыбыс деңгейінің жүгірткі реттегіштері жайылды."</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Дыбыс деңгейінің жүгірткі реттегіштері жиылды."</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s дыбысын өшіру"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s дыбысын қосу"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> ойнатылатын құрылғы:"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Аудио ойнатылатын құрылғы:"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Қоңырау шалып жатыр"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Жүйелік пайдаланушылық интерфейс тюнері"</string>
     <string name="status_bar" msgid="4357390266055077437">"Күйін көрсету жолағы"</string>
     <string name="demo_mode" msgid="263484519766901593">"Жүйе интерфейсінің демо режимі"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Жерсерік, байланыс нашар."</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Жерсерік, байланыс жақсы."</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Жерсерік, байланыс бар."</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Жұмыс профилі"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Кейбіреулерге қызық, бірақ барлығына емес"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Жүйелік пайдаланушылық интерфейс тюнері Android пайдаланушылық интерфейсін реттеудің қосымша жолдарын береді. Бұл эксперименттік мүмкіндіктер болашақ шығарылымдарда өзгеруі, бұзылуы немесе жоғалуы мүмкін. Сақтықпен жалғастырыңыз."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Бөлшек қосу"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> орнына жылжыту"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> орнына қосу"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Орын жарамсыз."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> орны"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Бөлшек қосылды."</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Бөлшек өшірілді."</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 4f1136b..dc9a747 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"បន្ទាត់បែងចែក​ខាងឆ្វេង <xliff:g id="PERCENT">%1$d</xliff:g> ភាគរយ"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"បន្ទាត់បែងចែក​ខាងស្ដាំ <xliff:g id="PERCENT">%1$d</xliff:g> ភាគរយ"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"បានរក្សាទុកនៅក្នុង <xliff:g id="APP">%1$s</xliff:g> ក្នុងកម្រងព័ត៌មានការងារ"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"បានរក្សាទុកនៅក្នុង <xliff:g id="APP">%1$s</xliff:g> ក្នុងកម្រងព័ត៌មានឯកជន"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ឯកសារ"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> បានរកឃើញ​រូបថតអេក្រង់នេះ។"</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> និងកម្មវិធីដែលបើក​ផ្សេងទៀតបានរកឃើញ​រូបថតអេក្រង់នេះ។"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ចុច​ឱ្យ​យូរ ដើម្បីប្ដូរធាតុ​ក្រាហ្វិកតាមបំណង"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ប្ដូរ​ធាតុ​ក្រាហ្វិកតាម​បំណង"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"រូបកម្មវិធីសម្រាប់ធាតុ​ក្រាហ្វិកដែលបានបិទ"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"កែធាតុ​ក្រាហ្វិក"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ដកចេញ"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"បញ្ចូលធាតុ​ក្រាហ្វិក"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"បិទធាតុ​ក្រាហ្វិកនៅលើអេក្រង់ចាក់សោ"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ប្ដូរ​ធាតុ​ក្រាហ្វិកតាម​បំណង"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ធាតុ​ក្រាហ្វិកនៅលើអេក្រង់ចាក់សោ"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ជ្រើសរើសធាតុ​ក្រាហ្វិក"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ដកធាតុ​ក្រាហ្វិកចេញ"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ដាក់ធាតុ​ក្រាហ្វិកដែលបានជ្រើសរើស"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ប្ដូរ​អ្នក​ប្រើ"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ម៉ឺនុយ​ទាញចុះ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"កម្មវិធី និងទិន្នន័យ​ទាំងអស់​ក្នុង​វគ្គ​នេះ​នឹង​ត្រូវ​លុប។"</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"ចូលការកំណត់ឧបករណ៍មេឌៀ"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"បានពង្រីកគ្រាប់រំកិលកម្រិតសំឡេង"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"បានបង្រួមគ្រាប់រំកិលកម្រិតសំឡេង"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"បិទសំឡេង %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"បើក​សំឡេង %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"កំពុងចាក់​​ <xliff:g id="LABEL">%s</xliff:g> នៅ​លើ"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"សំឡេងនឹងលេងនៅលើ"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"កំពុងនិយាយទូរសព្ទ"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"កម្មវិធីសម្រួល UI ប្រព័ន្ធ"</string>
     <string name="status_bar" msgid="4357390266055077437">"របារស្ថានភាព"</string>
     <string name="demo_mode" msgid="263484519766901593">"មុខងារ​សាកល្បង​ UI ប្រព័ន្ធ"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"ផ្កាយរណប ការតភ្ជាប់ខ្សោយ"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ផ្កាយរណប មានការតភ្ជាប់ល្អ"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ផ្កាយរណប អាចតភ្ជាប់បាន"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ការប្រកាសអាសន្នតាមផ្កាយរណប"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"កម្រងព័ត៌មានការងារ"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"ល្អសម្រាប់អ្នកប្រើមួយចំនួន តែមិនសម្រាប់គ្រប់គ្នាទេ"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"កម្មវិធីសម្រួល UI ប្រព័ន្ធផ្តល់ជូនអ្នកនូវមធ្យោបាយបន្ថែមទៀតដើម្បីកែសម្រួល និងប្តូរចំណុចប្រទាក់អ្នកប្រើ Android តាមបំណង។ លក្ខណៈពិសេសសាកល្បងនេះអាចនឹងផ្លាស់ប្តូរ បំបែក ឬបាត់បង់បន្ទាប់ពីការចេញផ្សាយនាពេលអនាគត។ សូមបន្តដោយប្រុងប្រយ័ត្ន។"</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"បញ្ចូល​ប្រអប់"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"ផ្លាស់​ទីទៅ <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"បញ្ចូលទៅ​ទីតាំងទី <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ទីតាំងគ្មានសុពលភាព។"</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ទីតាំងទី <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"បានបញ្ចូលប្រអប់"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"បាន​ផ្លាស់ទី​ប្រអប់"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 7174625..46a679c 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ಎಡಭಾಗದ ಬೌಂಡರಿ ಶೇಕಡಾ <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ಬಲಭಾಗದ ಬೌಂಡರಿ ಶೇಕಡಾ <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"ಕೆಲಸದ ಪ್ರೊಫೈಲ್‌ನಲ್ಲಿನ <xliff:g id="APP">%1$s</xliff:g> ನಲ್ಲಿ ಸೇವ್ ಮಾಡಲಾಗಿದೆ"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"ಖಾಸಗಿ ಪ್ರೊಫೈಲ್‌ನಲ್ಲಿನ <xliff:g id="APP">%1$s</xliff:g> ನಲ್ಲಿ ಸೇವ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ಫೈಲ್‌ಗಳು"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"ಈ ಸ್ಕ್ರೀನ್‌ಶಾಟ್ ಅನ್ನು <xliff:g id="APPNAME">%1$s</xliff:g> ಪತ್ತೆಹಚ್ಚಿದೆ."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> ಹಾಗೂ ತೆರೆದಿರುವ ಇತರ ಆ್ಯಪ್‌ಗಳು ಈ ಸ್ಕ್ರೀನ್‌ಶಾಟ್ ಅನ್ನು ಪತ್ತೆಹಚ್ಚಿವೆ."</string>
@@ -369,7 +368,7 @@
     <string name="quick_settings_contrast_standard" msgid="2538227821968061832">"ಪ್ರಮಾಣಿತ"</string>
     <string name="quick_settings_contrast_medium" msgid="5158352575583902566">"ಮಧ್ಯಮ"</string>
     <string name="quick_settings_contrast_high" msgid="656049259587494499">"ಹೆಚ್ಚು"</string>
-    <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"ಹಿಯರಿಂಗ್ ಸಾಧನಗಳು"</string>
+    <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"ಶ್ರವಣ ಸಾಧನಗಳು"</string>
     <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"ಹಿಯರಿಂಗ್ ಸಾಧನಗಳು"</string>
     <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"ಹೊಸ ಸಾಧನವನ್ನು ಪೇರ್ ಮಾಡಿ"</string>
     <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"ಹೊಸ ಸಾಧನವನ್ನು ಜೋಡಿಸಲು ಕ್ಲಿಕ್ ಮಾಡಿ"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ವಿಜೆಟ್‌ಗಳನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಲು ದೀರ್ಘಕಾಲ ಒತ್ತಿರಿ"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ವಿಜೆಟ್‌ಗಳನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಿ"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾದ ವಿಜೆಟ್‌ಗಾಗಿ ಆ್ಯಪ್ ಐಕಾನ್"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"ವಿಜೆಟ್ ಅನ್ನು ಎಡಿಟ್ ಮಾಡಿ"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ತೆಗೆದುಹಾಕಿ"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ವಿಜೆಟ್ ಅನ್ನು ಸೇರಿಸಿ"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"ಲಾಕ್ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ವಿಜೆಟ್‌ಗಳನ್ನು ಮುಚ್ಚಿರಿ"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ವಿಜೆಟ್‌ಗಳನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಿ"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ಲಾಕ್ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ವಿಜೆಟ್‌ಗಳು"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ವಿಜೆಟ್ ಅನ್ನು ಆಯ್ಕೆ ಮಾಡಿ"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ವಿಜೆಟ್ ಅನ್ನು ತೆಗೆದುಹಾಕಿ"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ಆಯ್ಕೆಮಾಡಿದ ವಿಜೆಟ್ ಅನ್ನು ಇರಿಸಿ"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ಬಳಕೆದಾರರನ್ನು ಬದಲಿಸಿ"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ಪುಲ್‌ಡೌನ್ ಮೆನು"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ಈ ಸೆಶನ್‌ನಲ್ಲಿನ ಎಲ್ಲಾ ಆ್ಯಪ್‌ಗಳು ಮತ್ತು ಡೇಟಾವನ್ನು ಅಳಿಸಲಾಗುತ್ತದೆ."</string>
@@ -592,9 +594,9 @@
     <string name="screen_pinning_negative" msgid="6882816864569211666">"ಬೇಡ"</string>
     <string name="screen_pinning_start" msgid="7483998671383371313">"ಆ್ಯಪ್ ಪಿನ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="screen_pinning_exit" msgid="4553787518387346893">"ಆ್ಯಪ್ ಅನ್‌ಪಿನ್ ಮಾಡಲಾಗಿದೆ"</string>
-    <string name="stream_voice_call" msgid="7468348170702375660">"ಕರೆಮಾಡಿ"</string>
+    <string name="stream_voice_call" msgid="7468348170702375660">"ಕರೆ ಮಾಡಿ"</string>
     <string name="stream_system" msgid="7663148785370565134">"ಸಿಸ್ಟಂ"</string>
-    <string name="stream_ring" msgid="7550670036738697526">"ರಿಂಗ್"</string>
+    <string name="stream_ring" msgid="7550670036738697526">"ರಿಂಗ್ ಮಾಡಿ"</string>
     <string name="stream_music" msgid="2188224742361847580">"ಮಾಧ್ಯಮ"</string>
     <string name="stream_alarm" msgid="16058075093011694">"ಅಲಾರಮ್"</string>
     <string name="stream_notification" msgid="7930294049046243939">"ನೋಟಿಫಿಕೇಶನ್"</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"ಔಟ್‌ಪುಟ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಪ್ರವೇಶಿಸಿ"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ವಾಲ್ಯೂಮ್ ಸ್ಲೈಡರ್‌ಗಳನ್ನು ವಿಸ್ತೃತಗೊಳಿಸಲಾಗಿದೆ"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ವಾಲ್ಯೂಮ್ ಸ್ಲೈಡರ್‌ಗಳನ್ನು ಕುಗ್ಗಿಸಲಾಗಿದೆ"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s ಮ್ಯೂಟ್ ಮಾಡಿ"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s ಅನ್‌ಮ್ಯೂಟ್ ಮಾಡಿ"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಆಗು..."</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"ಇದರಲ್ಲಿ ಪ್ಲೇ ಆಗುತ್ತದೆ"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"ಕರೆ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"ಸಿಸ್ಟಂ UI ಟ್ಯೂನರ್"</string>
     <string name="status_bar" msgid="4357390266055077437">"ಸ್ಥಿತಿ ಪಟ್ಟಿ"</string>
     <string name="demo_mode" msgid="263484519766901593">"ಸಿಸ್ಟಂ UI ಡೆಮೋ ಮೋಡ್"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"ಸ್ಯಾಟಲೈಟ್‌, ಕನೆಕ್ಷನ್ ಕಳಪೆಯಾಗಿದೆ"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ಸ್ಯಾಟಲೈಟ್‌, ಕನೆಕ್ಷನ್ ಉತ್ತಮವಾಗಿದೆ"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ಸ್ಯಾಟಲೈಟ್, ಕನೆಕ್ಷನ್ ಲಭ್ಯವಿದೆ"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ಸ್ಯಾಟಲೈಟ್ SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"ಕೆಲಸದ ಪ್ರೊಫೈಲ್"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"ಕೆಲವರಿಗೆ ಮೋಜು ಆಗಿದೆ ಎಲ್ಲರಿಗೆ ಇಲ್ಲ"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"ಸಿಸ್ಟಂ UI ಟ್ಯೂನರ್ ನಿಮಗೆ Android ಬಳಕೆದಾರ ಅಂತರಸಂಪರ್ಕವನ್ನು ಸರಿಪಡಿಸಲು ಮತ್ತು ಕಸ್ಟಮೈಸ್ ಮಾಡಲು ಹೆಚ್ಚುವರಿ ಮಾರ್ಗಗಳನ್ನು ನೀಡುತ್ತದೆ. ಈ ಪ್ರಾಯೋಗಿಕ ವೈಶಿಷ್ಟ್ಯಗಳು ಭವಿಷ್ಯದ ಬಿಡುಗಡೆಗಳಲ್ಲಿ ಬದಲಾಗಬಹುದು, ವಿರಾಮವಾಗಬಹುದು ಅಥವಾ ಕಾಣಿಸಿಕೊಳ್ಳದಿರಬಹುದು. ಎಚ್ಚರಿಕೆಯಿಂದ ಮುಂದುವರಿಯಿರಿ."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"ಟೈಲ್ ಸೇರಿಸಿ"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"ಇಲ್ಲಿಗೆ ಸರಿಸಿ <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> ಸ್ಥಾನಕ್ಕೆ ಸೇರಿಸಿ"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ಸ್ಥಾನವು ಅಮಾನ್ಯವಾಗಿದೆ."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ಸ್ಥಾನ <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ಟೈಲ್ ಸೇರಿಸಲಾಗಿದೆ"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ಟೈಲ್ ತೆಗೆದುಹಾಕಲಾಗಿದೆ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 55001db..54dcfce 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"왼쪽 가장자리 <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"오른쪽 가장자리 <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"직장 프로필의 <xliff:g id="APP">%1$s</xliff:g>에 저장되었습니다."</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"<xliff:g id="APP">%1$s</xliff:g>의 비공개 프로필에 저장됨"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"파일"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g>에서 이 스크린샷을 감지했습니다."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> 및 기타 공개 앱에서 이 스크린샷을 감지했습니다."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"위젯을 맞춤설정하려면 길게 누르기"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"위젯 맞춤설정"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"사용 중지된 위젯의 앱 아이콘"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"위젯 수정"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"삭제"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"위젯 추가"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"잠금 화면에서 위젯 닫기"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"위젯 맞춤설정"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"잠금 화면의 위젯"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"위젯 선택"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"위젯 삭제"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"선택한 위젯 배치"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"사용자 전환"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"풀다운 메뉴"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"이 세션에 있는 모든 앱과 데이터가 삭제됩니다."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"출력 설정 열기"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"볼륨 슬라이더 펼침"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"볼륨 슬라이더 접힘"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s 음소거"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s 음소거 해제"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> 재생 위치:"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"오디오 재생 위치:"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"전화 거는 중"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"시스템 UI 튜너"</string>
     <string name="status_bar" msgid="4357390266055077437">"상태 표시줄"</string>
     <string name="demo_mode" msgid="263484519766901593">"시스템 UI 데모 모드"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"위성, 연결 상태 나쁨"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"위성, 연결 상태 양호"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"위성, 연결 가능"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"위성 긴급 SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"직장 프로필"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"마음에 들지 않을 수도 있음"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"시스템 UI 튜너를 사용하면 Android 사용자 인터페이스를 변경 및 맞춤설정할 수 있습니다. 이러한 실험실 기능은 향후 출시 버전에서는 변경되거나 다운되거나 사라질 수 있습니다. 신중하게 진행하시기 바랍니다."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"타일 추가"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> 위치로 이동"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> 위치에 추가"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"위치가 잘못되었습니다."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> 위치"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"타일 추가됨"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"타일 삭제됨"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 7cd68d4..a224ac7 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Сол жагы <xliff:g id="PERCENT">%1$d</xliff:g> пайызга"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Оң жагы <xliff:g id="PERCENT">%1$d</xliff:g> пайызга"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Жумуш профилиндеги <xliff:g id="APP">%1$s</xliff:g> колдонмосуна сакталды"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Жеке профилдеги <xliff:g id="APP">%1$s</xliff:g> колдонмосуна сакталды"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ушул скриншотту аныктады."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> жана ачылып турган башка колдонмолор ушул скриншотту аныктады."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Виджеттерди ыңгайлаштыруу үчүн кое бербей басып туруңуз"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Виджеттерди ыңгайлаштыруу"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Өчүрүлгөн виджет үчүн колдонмонун сүрөтчөсү"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Виджетти түзөтүү"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Өчүрүү"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Виджет кошуу"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Виджеттерди кулпуланган экранда жабуу"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Виджеттерди ыңгайлаштыруу"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Кулпуланган экрандагы виджеттер"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"виджет тандоо"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"виджетти алып салуу"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"тандалган виджетти жайгаштыруу"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Колдонуучуну которуу"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ылдый түшүүчү меню"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Бул сеанстагы бардык колдонмолор жана аларга байланыштуу нерселер өчүрүлөт."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Чыгаруу параметрлерин киргизүү"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Үндүн катуулугунун сыдырмалары жайып көрсөтүлдү"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Үндүн катуулугунун сыдырмалары жыйыштырылды"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s үнүн басуу"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s үнүн чыгаруу"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> аркылуу ойнотулууда"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Аудио кайсы жерде ойнотулат:"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Чалууда"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"Абал тилкеси"</string>
     <string name="demo_mode" msgid="263484519766901593">"Системанын интерфейсинин демо режими"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Спутник, байланыш начар"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Спутник, байланыш жакшы"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Спутник, байланыш бар"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Спутник SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Жумуш профили"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Баарына эле жага бербейт"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner Android колдонуучу интерфейсин жөнгө салып жана ыңгайлаштыруунун кошумча ыкмаларын сунуштайт. Бул сынамык функциялар кийинки чыгарылыштарда өзгөрүлүп, бузулуп же жоголуп кетиши мүмкүн. Абайлап колдонуңуз."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Ыкчам баскыч кошуу"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Төмөнкүгө жылдыруу: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g>-позицияга кошуу"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Абал жараксыз."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>-позиция"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Карта кошулду"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Карта өчүрүлдү"</string>
diff --git a/packages/SystemUI/res/values-land/styles.xml b/packages/SystemUI/res/values-land/styles.xml
index 2769bea..73812c9 100644
--- a/packages/SystemUI/res/values-land/styles.xml
+++ b/packages/SystemUI/res/values-land/styles.xml
@@ -39,7 +39,7 @@
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Title">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">6dp</item>
         <item name="android:textSize">36dp</item>
         <item name="android:focusable">true</item>
@@ -47,14 +47,14 @@
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Subtitle">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">6dp</item>
         <item name="android:textSize">18sp</item>
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Description">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">6dp</item>
         <item name="android:textSize">18sp</item>
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index a8751ad..88866e3 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ຂອບເຂດທາງຊ້າຍ <xliff:g id="PERCENT">%1$d</xliff:g> ເປີເຊັນ"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ຂອບເຂດທາງຂວາ <xliff:g id="PERCENT">%1$d</xliff:g> ເປີເຊັນ"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"ບັນທຶກໃນ <xliff:g id="APP">%1$s</xliff:g> ໃນໂປຣໄຟລ໌ບ່ອນເຮັດວຽກແລ້ວ"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"ບັນທຶກໄວ້ໃນ <xliff:g id="APP">%1$s</xliff:g> ໃນໂປຣໄຟລ໌ສ່ວນຕົວແລ້ວ"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ໄຟລ໌"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ກວດພົບຮູບໜ້າຈໍນີ້."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> ແລະ ແອັບອື່ນໆທີ່ເປີດຢູ່ກວດພົບຮູບໜ້າຈໍນີ້."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ກົດຄ້າງໄວ້ເພື່ອປັບແຕ່ງວິດເຈັດ"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ປັບແຕ່ງວິດເຈັດ"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ໄອຄອນແອັບສຳລັບວິດເຈັດທີ່ຖືກປິດການນຳໃຊ້"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"ແກ້ໄຂວິດເຈັດ"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ລຶບອອກ"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ເພີ່ມວິດເຈັດ"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"ປິດວິດເຈັດຢູ່ໜ້າຈໍລັອກ"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ປັບແຕ່ງວິດເຈັດ"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ວິດເຈັດຢູ່ໜ້າຈໍລັອກ"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ເລືອກວິດເຈັດ"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ລຶບວິດເຈັດອອກ"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ວາງວິດເຈັດທີ່ເລືອກ"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ສະຫຼັບຜູ້ໃຊ້"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ເມນູແບບດຶງລົງ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ແອັບຯ​ແລະ​ຂໍ້​ມູນ​ທັງ​ໝົດ​ໃນ​ເຊດ​ຊັນ​ນີ້​ຈະ​ຖືກ​ລຶບ​ອອກ."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"ໃສ່ການຄັ້ງຄ່າເອົ້າພຸດ"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ຂະຫຍາຍສະໄລເດີລະດັບສຽງແລ້ວ"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ຫຍໍ້ສະໄລເດີລະດັບສຽງລົງແລ້ວ"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"ປິດສຽງ %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"ເຊົາປິດສຽງ %s"</string>
-    <string name="media_output_label_title" msgid="872824698593182505">"ກຳລັງຫຼິ້ນ <xliff:g id="LABEL">%s</xliff:g> ໃນ"</string>
-    <string name="media_output_title_without_playing" msgid="3825663683169305013">"ສຽງຈະຫຼິ້ນຕໍ່ໄປ"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
     <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
+    <string name="media_output_label_title" msgid="872824698593182505">"ກຳລັງຫຼິ້ນ <xliff:g id="LABEL">%s</xliff:g> ໃນ"</string>
+    <string name="media_output_title_without_playing" msgid="3825663683169305013">"ສຽງຈະຫຼິ້ນຢູ່"</string>
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"ກຳລັງໂທ"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"ແຖບສະຖານະ"</string>
     <string name="demo_mode" msgid="263484519766901593">"ໂໝດເດໂມສ່ວນຕິດຕໍ່ຜູ້ໃຊ້ລະບົບ"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"ດາວທຽມ, ການເຊື່ອມຕໍ່ບໍ່ດີ"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ດາວທຽມ, ການເຊື່ອມຕໍ່ດີ"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ດາວທຽມ, ການເຊື່ອມຕໍ່ທີ່ພ້ອມນຳໃຊ້"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS ດາວທຽມ"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"​ໂປຣ​ໄຟລ໌​ບ່ອນ​ເຮັດ​ວຽກ"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"ມ່ວນຊື່ນສຳລັບບາງຄົນ ແຕ່ບໍ່ແມ່ນສຳລັບທຸກຄົນ"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner ໃຫ້ທ່ານມີວິທີພິເສດຕື່ມອີກໃນການປັບປ່ຽນ ແລະຕົບແຕ່ງສ່ວນຕໍ່ປະສານຜູ້ໃຊ້ຂອງ Android. ຄຸນສົມບັດທົດລອງໃຊ້ເຫຼົ່ານີ້ອາດຈະປ່ຽນແປງ, ຢຸດເຊົາ ຫຼືຫາຍໄປໃນການວາງຈຳໜ່າຍໃນອະນາຄົດ. ຈົ່ງດຳເນີນຕໍ່ດ້ວຍຄວາມລະມັດລະວັງ."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"ເພີ່ມແຜ່ນ"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"ຍ້າຍໄປ <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"ເພີ່ມໃສ່ຕຳແໜ່ງ <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ຕຳແໜ່ງບໍ່ຖືກຕ້ອງ."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ຕຳແໜ່ງ <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ເພີ່ມແຜ່ນແລ້ວ"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ລຶບແຜ່ນແລ້ວ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index da07fdd..46ac304 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Kairioji riba – <xliff:g id="PERCENT">%1$d</xliff:g> proc."</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Dešinioji riba – <xliff:g id="PERCENT">%1$d</xliff:g> proc."</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Išsaugota programoje „<xliff:g id="APP">%1$s</xliff:g>“ darbo profilyje"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Išsaugota „<xliff:g id="APP">%1$s</xliff:g>“ privačiame profilyje"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Failai"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"„<xliff:g id="APPNAME">%1$s</xliff:g>“ aptiko šią ekrano kopiją."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"„<xliff:g id="APPNAME">%1$s</xliff:g>“ ir kitos atidarytos programos aptiko šią ekrano kopiją."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Ilgai paspauskite, kad tinkintumėte valdiklius"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Tinkinti valdiklius"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Išjungto valdiklio programos piktograma"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Redaguoti valdiklį"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Pašalinti"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Pridėti valdiklį"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Uždaryti valdiklius užrakinimo ekrane"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Tinkinti valdiklius"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Valdikliai užrakinimo ekrane"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"pasirinkite valdiklį"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"pašalinti valdiklį"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"padėti pasirinktą valdiklį"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Perjungti naudotoją"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"išplečiamasis meniu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bus ištrintos visos šios sesijos programos ir duomenys."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Įveskite išvesties nustatymus"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Garsumo šliaužikliai išskleisti"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Garsumo šliaužikliai sutraukti"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"nutildyti %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"įjungti garsą %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Leidžiama „<xliff:g id="LABEL">%s</xliff:g>“"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Garsas bus leidžiamas"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Skambinama"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Sistemos naudotojo sąsajos derinimo priemonė"</string>
     <string name="status_bar" msgid="4357390266055077437">"Būsenos juosta"</string>
     <string name="demo_mode" msgid="263484519766901593">"Sistemos NS demonstracinis režimas"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Palydovas, prastas ryšys"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Palydovas, geras ryšys"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Palydovas, pasiekiamas ryšys"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Prisijungimas prie palydovo kritiniu atveju"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Darbo profilis"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Smagu, bet ne visada"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Sistemos naudotojo sąsajos derinimo priemonė suteikia papildomų galimybių pagerinti ir tinkinti „Android“ naudotojo sąsają. Šios eksperimentinės funkcijos gali pasikeisti, nutrūkti ar išnykti iš būsimų laidų. Tęskite atsargiai."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Pridėti išklotinės elementą"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Perkelkite į <xliff:g id="POSITION">%1$d</xliff:g> poziciją"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Pridėkite <xliff:g id="POSITION">%1$d</xliff:g> pozicijoje"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Padėtis netinkama."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> pozicija"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Išklotinė pridėta"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Išklotinė pašalinta"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 7e57d8e..979f59e 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Kreisā mala: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Labā mala: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Saglabāts lietotnē <xliff:g id="APP">%1$s</xliff:g> darba profilā"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Saglabāts privātā profilā lietotnē <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Faili"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> konstatēja, ka tika veikts ekrānuzņēmums."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> un citas atvērtas lietotnes konstatēja, ka tika veikts ekrānuzņēmums."</string>
@@ -371,7 +370,7 @@
     <string name="quick_settings_contrast_high" msgid="656049259587494499">"Augsts"</string>
     <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"Dzirdes aparāti"</string>
     <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"Dzirdes aparāti"</string>
-    <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Savienojiet pārī jaunu ierīci"</string>
+    <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Savienot pārī jaunu ierīci"</string>
     <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Noklikšķiniet, lai savienotu pārī jaunu ierīci"</string>
     <string name="hearing_devices_presets_error" msgid="350363093458408536">"Nevarēja atjaunināt pirmsiestatījumu"</string>
     <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Vai atbloķēt ierīces mikrofonu?"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Nospiediet un turiet, lai pielāgotu logrīkus."</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Pielāgot logrīkus"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Lietotnes ikona atspējotam logrīkam"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Rediģēt logrīku"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Noņemt"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Pievienot logrīku"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Aizvērt logrīkus bloķēšanas ekrānā"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Pielāgot logrīkus"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Logrīki bloķēšanas ekrānā"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"atlasīt logrīku"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"noņemt logrīku"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"novietot atlasīto logrīku"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Mainīt lietotāju"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"novelkamā izvēlne"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tiks dzēstas visas šīs sesijas lietotnes un dati."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Atvērt izvades iestatījumus"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Skaļuma slīdņi izvērsti"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Skaļuma slīdņi sakļauti"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"izslēgt skaņu straumei %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"ieslēgt skaņu straumei %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> — atskaņošana šeit:"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio tiks atskaņots šeit:"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Aktīvs zvans ierīcē"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Sistēmas saskarnes regulators"</string>
     <string name="status_bar" msgid="4357390266055077437">"Statusa josla"</string>
     <string name="demo_mode" msgid="263484519766901593">"Sistēmas lietotāja saskarnes demonstrācijas režīms"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelīts, vājš savienojums"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelīts, labs savienojums"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelīts, ir pieejams savienojums"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satelīta SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Darba profils"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Jautri dažiem, bet ne visiem"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Sistēmas saskarnes regulators sniedz papildu veidus, kā mainīt un pielāgot Android lietotāja saskarni. Nākamajās versijās šīs eksperimentālās funkcijas var tikt mainītas, bojātas vai to darbība var tikt pārtraukta. Turpinot esiet uzmanīgs."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Pievienot elementu"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Pārvietot uz pozīciju numur <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Pievienot elementu pozīcijā numur <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Nederīga pozīcija."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozīcija numur <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Elements ir pievienots"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Elements ir noņemts"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index e1a979e..e305c9e 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Лева граница <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Десна граница <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Зачувано во <xliff:g id="APP">%1$s</xliff:g> во работниот профил"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Зачувано во <xliff:g id="APP">%1$s</xliff:g> во приватниот профил"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Датотеки"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ја откри оваа слика од екранот."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> и други отворени апликации ја открија оваа слика од екранот."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Притиснете долго за да ги приспособите виџетите"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Приспособете ги виџетите"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Икона за апликација за оневозможен виџет"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Изменување виџети"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Отстранува"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Додајте виџет"</string>
@@ -461,7 +462,10 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Затворете ги виџетите на заклучениот екран"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Приспособете ги виџетите"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Виџети на заклучен екран"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"изберете виџет"</string>
+    <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) -->
+    <skip />
+    <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) -->
     <skip />
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Промени го корисникот"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"паѓачко мени"</string>
@@ -594,14 +598,14 @@
     <string name="screen_pinning_exit" msgid="4553787518387346893">"Апликацијата е откачена"</string>
     <string name="stream_voice_call" msgid="7468348170702375660">"Повик"</string>
     <string name="stream_system" msgid="7663148785370565134">"Систем"</string>
-    <string name="stream_ring" msgid="7550670036738697526">"Ѕвони"</string>
+    <string name="stream_ring" msgid="7550670036738697526">"Ѕвонење"</string>
     <string name="stream_music" msgid="2188224742361847580">"Аудиовизуелни содржини"</string>
     <string name="stream_alarm" msgid="16058075093011694">"Аларм"</string>
     <string name="stream_notification" msgid="7930294049046243939">"Известување"</string>
     <string name="stream_bluetooth_sco" msgid="6234562365528664331">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="7322536356554673067">"Двојна повеќетонска фреквенција"</string>
     <string name="stream_accessibility" msgid="3873610336741987152">"Пристапност"</string>
-    <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ѕвони"</string>
+    <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ѕвонење"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Вибрации"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Исклучи звук"</string>
     <string name="media_device_cast" msgid="4786241789687569892">"Емитување"</string>
@@ -627,12 +631,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Внесете ги поставките за излез"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Лизгачите за јачина на звукот се проширени"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Лизгачите за јачина на звукот се собрани"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"исклучување звук на %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"вклучување звук на %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g>: пуштено на"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Аудиото ќе се пушти на"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Повик во тек"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Адаптер на УИ на системот"</string>
     <string name="status_bar" msgid="4357390266055077437">"Статусна лента"</string>
     <string name="demo_mode" msgid="263484519766901593">"Демо-режим на кориснички интерфејс на систем"</string>
@@ -660,8 +669,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Слаба сателитска врска"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Добра сателитска врска"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Достапна е сателитска врска"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Сателитски SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Работен профил"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Забава за некои, но не за сите"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Адаптерот на УИ на системот ви дава дополнителни начини за дотерување и приспособување на корисничкиот интерфејс на Android. Овие експериментални функции можеби ќе се изменат, расипат или ќе исчезнат во следните изданија. Продолжете со претпазливост."</string>
@@ -866,8 +874,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Додавање плочка"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Преместување на <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Додавање на позиција <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Позицијата е погрешна."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Позиција <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Додадена е плочка"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Отстранета е плочка"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 4e0acb0..c14cc41 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ഇടത് വശത്തെ അതിർത്തി <xliff:g id="PERCENT">%1$d</xliff:g> ശതമാനം"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"വലത് വശത്തെ അതിർത്തി <xliff:g id="PERCENT">%1$d</xliff:g> ശതമാനം"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"ഔദ്യോഗിക പ്രൊഫൈലിൽ <xliff:g id="APP">%1$s</xliff:g> ആപ്പിൽ സംരക്ഷിച്ചു"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"സ്വകാര്യ പ്രൊഫൈലിൽ <xliff:g id="APP">%1$s</xliff:g> ആപ്പിൽ സംരക്ഷിച്ചു"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ഫയലുകൾ"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ഈ സ്ക്രീൻഷോട്ട് തിരിച്ചറിഞ്ഞു."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> എന്ന ആപ്പും തുറന്നിരിക്കുന്ന മറ്റ് ആപ്പും ഈ സ്ക്രീൻഷോട്ട് തിരിച്ചറിഞ്ഞു."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"വിജറ്റുകൾ ഇഷ്ടാനുസൃതമാക്കാൻ ദീർഘനേരം അമർത്തുക"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"വിജറ്റുകൾ ഇഷ്ടാനുസൃതമാക്കുക"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"പ്രവർത്തനരഹിതമാക്കിയ വിജറ്റിനുള്ള ആപ്പ് ഐക്കൺ"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"വിജറ്റ് എഡിറ്റ് ചെയ്യുക"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"നീക്കം ചെയ്യുക"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"വിജറ്റ് ചേർക്കുക"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"ലോക്ക് സ്ക്രീനിൽ വിജറ്റുകൾ അടയ്ക്കുക"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"വിജറ്റുകൾ ഇഷ്ടാനുസൃതമാക്കുക"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ലോക്ക് സ്‌ക്രീനിൽ വിജറ്റുകൾ"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"വിജറ്റ് തിരഞ്ഞെടുക്കുക"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"വിജറ്റ് നീക്കം ചെയ്യുക"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"തിരഞ്ഞെടുത്ത വിജറ്റ് നൽകുക"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ഉപയോക്താവ് മാറുക"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"പുൾഡൗൺ മെനു"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ഈ സെഷനിലെ എല്ലാ ആപ്പുകളും ഡാറ്റയും ഇല്ലാതാക്കും."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"ഔട്ട്പുട്ട് ക്രമീകരണം നൽകുക"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"വോളിയം സ്ലൈഡറുകൾ വികസിപ്പിച്ചു"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"വോളിയം സ്ലൈഡറുകൾ ചുരുക്കി"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s മ്യൂട്ട് ചെയ്യുക"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s അൺമ്യൂട്ട് ചെയ്യുക"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> എന്നതിൽ പ്ലേ ചെയ്യുന്നു"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"ഓഡിയോ പ്ലേ ചെയ്യും"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"കോൾ പുരോഗമിക്കുന്നു"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"സിസ്റ്റം UI ട്യൂണർ"</string>
     <string name="status_bar" msgid="4357390266055077437">"സ്റ്റാറ്റസ് ബാർ"</string>
     <string name="demo_mode" msgid="263484519766901593">"സിസ്റ്റം UI ഡെമോ മോഡ്"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"സാറ്റലൈറ്റ്, മോശം കണക്ഷൻ"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"സാറ്റലൈറ്റ്, മികച്ച കണക്ഷൻ"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"സാറ്റലൈറ്റ്, കണക്ഷൻ ലഭ്യമാണ്"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"സാറ്റലൈറ്റ് SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"ഔദ്യോഗിക പ്രൊഫൈൽ"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"ചിലർക്ക് വിനോദം, എന്നാൽ എല്ലാവർക്കുമില്ല"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Android ഉപയോക്തൃ ഇന്റർഫേസ് ആവശ്യമുള്ള രീതിയിൽ മാറ്റുന്നതിനും ഇഷ്ടാനുസൃതമാക്കുന്നതിനും സിസ്റ്റം UI ട്യൂണർ നിങ്ങൾക്ക് അധിക വഴികൾ നൽകുന്നു. ഭാവി റിലീസുകളിൽ ഈ പരീക്ഷണാത്മക ഫീച്ചറുകൾ മാറ്റുകയോ നിർത്തുകയോ അപ്രത്യക്ഷമാവുകയോ ചെയ്തേക്കാം. ശ്രദ്ധയോടെ മുന്നോട്ടുപോകുക."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"ടൈൽ ചേർക്കുക"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> എന്നതിലേക്ക് നീക്കുക"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> എന്ന സ്ഥാനത്തേക്ക് ചേർക്കുക"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"സ്ഥാനം അസാധുവാണ്."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"സ്ഥാനം <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ടൈൽ ചേർത്തു"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ടൈൽ നീക്കം ചെയ്‌തു"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 1be3f92..573c8ff 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Зүүн талын хязгаар <xliff:g id="PERCENT">%1$d</xliff:g> хувь"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Баруун талын хязгаар <xliff:g id="PERCENT">%1$d</xliff:g> хувь"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Ажлын профайл дахь <xliff:g id="APP">%1$s</xliff:g>-д хадгалсан"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Хувийн профайл дахь <xliff:g id="APP">%1$s</xliff:g>-д хадгалсан"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Файлс"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> энэ дэлгэцийн агшныг илрүүлсэн."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> болон бусад нээлттэй апп энэ дэлгэцийн агшныг илрүүлсэн."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Виджетүүдийг өөрчлөхийн тулд удаан дарна уу"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Виджетүүдийг өөрчлөх"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Идэвхгүй болгосон виджетийн аппын дүрс тэмдэг"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Виджетийг засах"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Хасах"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Виджет нэмэх"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Түгжээтэй дэлгэц дээр виджетүүдийг хаах"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Виджетийг өөрчлөх"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Түгжээтэй дэлгэц дээрх виджетүүд"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"виджет сонгох"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"виджетийг хасах"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"сонгосон виджетийг байрлуулах"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Хэрэглэгчийг сэлгэх"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"эвхмэл цэс"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Энэ харилцан үйлдлийн бүх апп болон дата устах болно."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Оролтын тохиргоог оруулах"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Дууны түвшний гулсуулагчдыг дэлгэсэн"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Дууны түвшний гулсуулагчдыг хураасан"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s-н дууг хаах"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s-н дууг нээх"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> тоглуулж байна"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Аудиог дараахад тоглуулна"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Дуудлага хийгдэж буй:"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Системийн UI Тохируулагч"</string>
     <string name="status_bar" msgid="4357390266055077437">"Статус самбар"</string>
     <string name="demo_mode" msgid="263484519766901593">"Системийн UI демо горим"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Хиймэл дагуул, холболт муу байна"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Хиймэл дагуул, холболт сайн байна"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Хиймэл дагуул, холболт боломжтой"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Хиймэл дагуул SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Ажлын профайл"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Зарим хүнд хөгжилтэй байж болох ч бүх хүнд тийм биш"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Системийн UI Tохируулагч нь Android хэрэглэгчийн интерфэйсийг тааруулах, өөрчлөх нэмэлт аргыг зааж өгөх болно. Эдгээр туршилтын тохиргоо нь цаашид өөрчлөгдөх, эвдрэх, алга болох магадлалтай. Үйлдлийг болгоомжтой хийнэ үү."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Хавтан нэмэх"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> руу зөөнө үү"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> байрлалд нэмнэ үү"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Байрлал буруу байна."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> байрлал"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Хавтан нэмсэн"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Хавтанг хассан"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index f3ab5b9..3667f1e 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"डाव्या सीमेपासून <xliff:g id="PERCENT">%1$d</xliff:g> टक्के"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"उजव्या सीमेपासून <xliff:g id="PERCENT">%1$d</xliff:g> टक्के"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"<xliff:g id="APP">%1$s</xliff:g> मधील कार्य प्रोफाइलमध्ये सेव्ह केला"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"<xliff:g id="APP">%1$s</xliff:g> मधील खाजगी प्रोफाइलमध्ये सेव्ह केले आहे"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"फाइल"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ने हा स्क्रीनशॉट डिटेक्ट केला."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> आणि उघडलेल्या इतर अ‍ॅप्सनी हा स्क्रीनशॉट डिटेक्ट केला."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"विजेट कस्टमाइझ करण्यासाठी प्रेस करून ठेवा"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"विजेट कस्टमाइझ करा"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"बंद केलेल्या विजेटच्या अ‍ॅपचे आयकन"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"विजेट संपादित करा"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"काढून टाका"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"विजेट जोडा"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"लॉक स्क्रीनवरील विजेट बंद करा"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"विजेट कस्टमाइझ करा"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"लॉक स्क्रीनवरील विजेट"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"विजेट निवडा"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"विजेट काढून टाका"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"निवडलेले विजेट ठेवा"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"वापरकर्ता स्विच करा"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेनू"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"या सत्रातील सर्व अ‍ॅप्स आणि डेटा हटवला जाईल."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"आउटपुट सेटिंग्ज एंटर करा"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"व्हॉल्यूम स्लायडर विस्तारित केले आहेत"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"व्हॉल्यूम स्लायडर कोलॅप्स केले आहेत"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s म्यूट करा"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s अनम्यूट करा"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> वर प्ले करत आहे"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"यावर ऑडिओ प्ले होईल"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"यावर कॉल करत आहे"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"सिस्टम UI ट्युनर"</string>
     <string name="status_bar" msgid="4357390266055077437">"स्टेटस बार"</string>
     <string name="demo_mode" msgid="263484519766901593">"सिस्टम UI डेमो मोड"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"सॅटेलाइट, खराब कनेक्शन"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"सॅटेलाइट, चांगले कनेक्शन"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"सॅटेलाइट, कनेक्शन उपलब्ध"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"सॅटेलाइट SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"कार्य प्रोफाईल"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"सर्वांसाठी नाही तर काहींसाठी मजेदार असू शकते"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"सिस्टम UI ट्युनर आपल्‍याला Android यूझर इंटरफेस ट्विक आणि कस्टमाइझ करण्‍याचे अनेक प्रकार देते. ही प्रयोगात्मक वैशिष्‍ट्ये बदलू शकतात, खंडित होऊ शकतात किंवा भविष्‍यातील रिलीज मध्‍ये कदाचित दिसणार नाहीत. सावधगिरी बाळगून पुढे सुरू ठेवा."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"टाइल जोडा"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> यावर हलवा"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> स्थानावर जोडा"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"स्थान चुकीचे आहे."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"स्थान <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"टाइल जोडली"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"टाइल काढून टाकली"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index d60ff5a..60aedfa 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Sempadan kiri <xliff:g id="PERCENT">%1$d</xliff:g> peratus"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Sempadan kanan <xliff:g id="PERCENT">%1$d</xliff:g> peratus"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Disimpan dalam <xliff:g id="APP">%1$s</xliff:g> dalam profil kerja"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Disimpan dalam <xliff:g id="APP">%1$s</xliff:g> pada profil peribadi"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fail"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> telah mengesan tangkapan skrin ini."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> dan apl lain yang dibuka telah mengesan tangkapan skrin ini."</string>
@@ -237,7 +236,7 @@
     <string name="accessibility_desc_notification_shade" msgid="5355229129428759989">"Bidai pemberitahuan."</string>
     <string name="accessibility_desc_quick_settings" msgid="4374766941484719179">"Tetapan pantas."</string>
     <string name="accessibility_desc_qs_notification_shade" msgid="8327226953072700376">"Tetapan pantas dan Bidai pemberitahuan."</string>
-    <string name="accessibility_desc_lock_screen" msgid="409034672704273634">"Kunci skrin"</string>
+    <string name="accessibility_desc_lock_screen" msgid="409034672704273634">"Skrin kunci"</string>
     <string name="accessibility_desc_work_lock" msgid="4355620395354680575">"Skrin kunci kerja"</string>
     <string name="accessibility_desc_close" msgid="8293708213442107755">"Tutup"</string>
     <string name="accessibility_quick_settings_dnd_none_on" msgid="3235552940146035383">"senyap sepenuhnya"</string>
@@ -276,7 +275,7 @@
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Disimpan"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"putuskan sambungan"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktifkan"</string>
-    <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Dihidupkan sekali lagi esok secara automatik"</string>
+    <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Dihidupkan lagi esok secara automatik"</string>
     <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Ciri seperti Quick Share dan Find My Device menggunakan Bluetooth"</string>
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth akan dihidupkan esok pagi"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"Perkongsian Audio"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Tekan lama untuk menyesuaikan widget"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Sesuaikan widget"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikon apl untuk melumpuhkan widget"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Alih keluar"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Tambahkan widget"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Tutup widget pada skrin kunci"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Sesuaikan widget"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widget pada skrin kunci"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"pilih widget"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"alih keluar widget"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"letakkan widget dipilih"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Tukar pengguna"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu tarik turun"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Semua apl dan data dalam sesi ini akan dipadam."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Masukkan tetapan output"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Peluncur kelantangan dikembangkan"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Peluncur kelantangan dikuncupkan"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"redamkan %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"nyahredamkan %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Memainkan <xliff:g id="LABEL">%s</xliff:g> pada"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio dimainkan pada"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Membuat panggilan"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Penala UI Sistem"</string>
     <string name="status_bar" msgid="4357390266055077437">"Bar status"</string>
     <string name="demo_mode" msgid="263484519766901593">"Mod tunjuk cara UI sistem"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelit, sambungan yang lemah"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, sambungan yang baik"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, sambungan tersedia"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS via Satelit"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profil kerja"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Menarik untuk sesetengah orang tetapi bukan untuk semua"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Penala UI Sistem memberi anda cara tambahan untuk mengolah dan menyesuaikan antara muka Android. Ciri eksperimen ini boleh berubah, rosak atau hilang dalam keluaran masa hadapan. Teruskan dengan berhati-hati."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Tambahkan jubin"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Alih ke <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Tambahkan pada kedudukan <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Kedudukan tidak sah."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Kedudukan <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Jubin ditambah"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Jubin dialih keluar"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 42b3602..b7398d0 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ဘယ်ဘက်အနားသတ် <xliff:g id="PERCENT">%1$d</xliff:g> ရာခိုင်နှုန်း"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ညာဘက်အနားသတ် <xliff:g id="PERCENT">%1$d</xliff:g> ရာခိုင်နှုန်း"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"အလုပ်ပရိုဖိုင်ရှိ <xliff:g id="APP">%1$s</xliff:g> တွင် သိမ်းထားသည်"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"သီးသန့် ပရိုဖိုင်ရှိ <xliff:g id="APP">%1$s</xliff:g> တွင် သိမ်းထားသည်"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ဖိုင်များ"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> က ဤဖန်သားပြင်ဓာတ်ပုံကို တွေ့ရှိသည်။"</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> နှင့် အခြားဖွင့်ထားသော အက်ပ်များက ဤဖန်သားပြင်ဓာတ်ပုံကို တွေ့ရှိသည်။"</string>
@@ -276,7 +275,7 @@
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"သိမ်းထားသည်"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ချိတ်ဆက်မှုဖြုတ်ရန်"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"စသုံးရန်"</string>
-    <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"မနက်ဖြန် အလိုအလျောက် ထပ်ဖွင့်ရန်"</string>
+    <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"မနက်ဖြန် အလိုအလျောက် ပြန်ဖွင့်ရန်"</string>
     <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"‘အမြန် မျှဝေပါ’ နှင့် Find My Device ကဲ့သို့ တူးလ်များသည် ဘလူးတုသ်သုံးသည်"</string>
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"မနက်ဖြန်နံနက်တွင် ဘလူးတုသ် ပွင့်ပါမည်"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"အော်ဒီယို မျှဝေခြင်း"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ဝိဂျက်များ စိတ်ကြိုက်လုပ်ရန် ကြာကြာနှိပ်ထားပါ"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ဝိဂျက်များကို စိတ်ကြိုက်လုပ်ရန်"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ပိတ်ထားသော ဝိဂျက်အတွက် အက်ပ်သင်္ကေတ"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"ဝိဂျက်ပြင်ရန်"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ဖယ်ရှားရန်"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ဝိဂျက်ထည့်ရန်"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"လော့ခ်မျက်နှာပြင်ရှိ ဝိဂျက်များကို ပိတ်ရန်"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ဝိဂျက်များကို စိတ်ကြိုက်လုပ်ရန်"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"လော့ခ်မျက်နှာပြင်ရှိ ဝိဂျက်များ"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ဝိဂျက် ရွေးရန်"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ဝိဂျက် ဖယ်ရှားရန်"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ရွေးချယ်ထားသော ဝိဂျက်ကို တင်ရန်"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"အသုံးပြုသူကို ပြောင်းလဲရန်"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ဆွဲချမီနူး"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ဒီချိတ်ဆက်မှု ထဲက အက်ပ်များ အားလုံး နှင့် ဒေတာကို ဖျက်ပစ်မည်။"</string>
@@ -617,7 +619,7 @@
     <string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"ထောင့်စုံအော်ဒီယို"</string>
     <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"ပိတ်"</string>
     <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"ပုံသေ"</string>
-    <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"ခေါင်းလှုပ်ရှားမှု စောင့်ကြည့်ခြင်း"</string>
+    <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"ခေါင်းလှုပ်ရှားမှု"</string>
     <string name="volume_ringer_change" msgid="3574969197796055532">"ဖုန်းခေါ်သံမုဒ်သို့ ပြောင်းရန် တို့ပါ"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"အသံပိတ်ရန်"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"အသံဖွင့်ရန်"</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"အထွက် ဆက်တင်များ ထည့်ရန်"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"အသံအတိုးအကျယ် ရွှေ့တုံးများ ပိုပြထားသည်"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"အသံအတိုးအကျယ် ရွှေ့တုံးများ လျှော့ပြထားသည်"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s အသံပိတ်ရန်"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s အသံပြန်ဖွင့်ရန်"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> ဖွင့်မည့်နေရာ"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"အသံဖွင့်မည့်နေရာ"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"ဖုန်းဆက်နေသည်"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"စနစ် UI ဖမ်းစက်"</string>
     <string name="status_bar" msgid="4357390266055077437">"အခြေအနေပြနေရာ"</string>
     <string name="demo_mode" msgid="263484519766901593">"စနစ် UI စရုပ်ပြမုဒ်"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"ဂြိုဟ်တု၊ ချိတ်ဆက်မှု မကောင်းပါ"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ဂြိုဟ်တု၊ ချိတ်ဆက်မှု ကောင်းသည်"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ဂြိုဟ်တု၊ ချိတ်ဆက်မှု ရနိုင်သည်"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"အလုပ် ပရိုဖိုင်"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"အချို့သူများ အတွက် ပျော်စရာ ဖြစ်ပေမဲ့ အားလုံး အတွက် မဟုတ်ပါ"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"စနစ် UI Tuner က သင့်အတွက် Android အသုံးပြုသူ အင်တာဖေ့စ်ကို ပြောင်းရန်နှင့် စိတ်ကြိုက်ပြုလုပ်ရန် နည်းလမ်း အပိုများကို သင့်အတွက် စီစဉ်ပေးသည်။ အနာဂတ်ဗားရှင်းများတွင် ဤစမ်းသပ်အင်္ဂါရပ်များမှာ ပြောင်းလဲ၊ ပျက်စီး သို့မဟုတ် ပျောက်ကွယ်သွားနိုင်သည်။ သတိဖြင့် ရှေ့ဆက်ပါ။"</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"အကွက်ငယ်ကို ထည့်ရန်"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> သို့ ရွှေ့ရန်"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> အနေအထားသို့ ပေါင်းထည့်ရန်"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"နေရာ မမှန်ပါ။"</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> အနေအထား"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"အကွက်ငယ်ကို ထည့်ပြီးပါပြီ"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"အကွက်ငယ်ကို ဖယ်ရှားပြီးပါပြီ"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index fec8a44..2b131c7 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Venstre grense <xliff:g id="PERCENT">%1$d</xliff:g> prosent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Høyre grense <xliff:g id="PERCENT">%1$d</xliff:g> prosent"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Lagret i <xliff:g id="APP">%1$s</xliff:g> i jobbprofilen"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Lagret i <xliff:g id="APP">%1$s</xliff:g> i den private profilen"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Filer"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> har registrert denne skjermdumpen."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> og andre åpne apper har registrert denne skjermdumpen."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Trykk lenge for å tilpasse modulene"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Tilpass moduler"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Appikon for deaktivert modul"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Endre modul"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Fjern"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Legg til modul"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Lukk moduler på låseskjermen"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Tilpass moduler"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Moduler på låseskjermen"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"velg modul"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"fjern modul"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"plasser den valgte modulen"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Bytt bruker"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullegardinmeny"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apper og data i denne økten blir slettet."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Angi utdatainnstillinger"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Glidebrytere for volum er skjult"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Glidebrytere for volum er skjult"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"kutt lyden til %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"slå på lyden til %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Spiller av <xliff:g id="LABEL">%s</xliff:g> på"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Lyden spilles av på"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Aktiv samtale på"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"Statusrad"</string>
     <string name="demo_mode" msgid="263484519766901593">"Demomodus for systemgrensesnitt"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satellitt – dårlig tilkobling"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellitt – god tilkobling"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellitt – tilkobling tilgjengelig"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS-alarm via satellitt"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Work-profil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Gøy for noen – ikke for alle"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Med System UI Tuner har du flere måter å justere og tilpasse Android-brukergrensesnittet på. Disse eksperimentelle funksjonene kan endres, avbrytes eller fjernes i fremtidige utgivelser. Fortsett med forbehold."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Legg til en infobrikke"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Flytt til <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Legg til posisjonen <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posisjonen er ugyldig."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posisjon <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"En infobrikke er lagt til"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"En infobrikke er fjernet"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 3062263..42abd44 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -175,7 +175,7 @@
     <string name="biometric_dialog_wrong_pattern" msgid="8954812279840889029">"प्याटर्न मिलेन"</string>
     <string name="biometric_dialog_wrong_password" msgid="69477929306843790">"पासवर्ड मिलेन"</string>
     <string name="biometric_dialog_credential_too_many_attempts" msgid="3083141271737748716">"अत्यन्तै धेरै पटक गलत प्रयास गरिए। \n <xliff:g id="NUMBER">%d</xliff:g>सेकेन्ड पछि पुनः प्रयास गर्नुहोस्।"</string>
-    <string name="work_challenge_emergency_button_text" msgid="8946588434515599288">"आपत्कालीन"</string>
+    <string name="work_challenge_emergency_button_text" msgid="8946588434515599288">"आपत्‌कालीन"</string>
     <string name="biometric_dialog_credential_attempts_before_wipe" msgid="6751859711975516999">"फेरि प्रयास गर्नुहोस्। <xliff:g id="MAX_ATTEMPTS">%2$d</xliff:g> मध्ये <xliff:g id="ATTEMPTS_0">%1$d</xliff:g> प्रयास।"</string>
     <string name="biometric_dialog_last_attempt_before_wipe_dialog_title" msgid="2874250099278693477">"तपाईंको डेटा मेटाइने छ"</string>
     <string name="biometric_dialog_last_pattern_attempt_before_wipe_device" msgid="6562299244825817598">"तपाईंले अर्को पटक पनि गलत ढाँचा प्रविष्टि गर्नुभयो भने यो डिभाइसको डेटा मेटाइने छ।"</string>
@@ -203,10 +203,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"फेस अनलक सेटअप गर्न सकिएन। फेरि प्रयास गर्न सेटिङमा जानुहोस्।"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"फिंगरप्रिन्ट सेन्सरमा छुनुहोस्‌"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"जारी राख्न अनलक आइकनमा थिच्नुहोस्"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"अनुहार पहिचान गर्न सकिएन। बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्।"</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"अनुहार मिलेन। बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्।"</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="2346762871330729634">"अनुहार पहिचान गर्न सकिएन"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"अनुहार मिलेन"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"फेस अनलक उपलब्ध छैन"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लुटुथ जडान भयो।"</string>
@@ -447,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"विजेटहरू कस्टमाइज गर्न केही बेरसम्म थिच्नुहोस्"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"विजेटहरू कस्टमाइज गर्नुहोस्"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"अफ गरिएको विजेटको एप जनाउने आइकन"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"विजेट सम्पादन गर्नुहोस्"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"हटाउनुहोस्"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"विजेट हाल्नुहोस्"</string>
@@ -461,6 +463,8 @@
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"विजेटहरू कस्टमाइज गर्नुहोस्"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"लक स्क्रिनमा भएका विजेटहरू"</string>
     <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"विजेट चयन गर्नुहोस्"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"विजेट हटाउनुहोस्"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"चयन गरिएका विजेटका लागि ठाउँ चयन गर्नुहोस्"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"प्रयोगकर्ता फेर्नुहोस्"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेनु"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"यो सत्रमा भएका सबै एपहरू र डेटा मेटाइने छ।"</string>
@@ -625,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"आउटपुटसम्बन्धी सेटिङमा जानुहोस्"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"भोल्युम स्लाइडरहरू एक्स्पान्ड गरिएका छन्"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"भोल्युम स्लाइडरहरू कोल्याप्स गरिएका छन्"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s म्युट गर्नुहोस्"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s अनम्युट गर्नुहोस्"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> प्ले गरिँदै छ"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"अडियो यसमा प्ले हुने छ"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"कल चलिरहेको छ"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"सिस्टम UI ट्युनर"</string>
     <string name="status_bar" msgid="4357390266055077437">"स्थिति पट्टी"</string>
     <string name="demo_mode" msgid="263484519766901593">"सिस्टम UI को डेमो मोड"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 031fabc..c459b93 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Linkergrens <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Rechtergrens <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Opgeslagen in <xliff:g id="APP">%1$s</xliff:g> in het werkprofiel."</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Opgeslagen in <xliff:g id="APP">%1$s</xliff:g> in het privéprofiel"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Bestanden"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> heeft dit screenshot waargenomen."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> en andere geopende apps hebben dit screenshot waargenomen."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Houd lang ingedrukt om widgets aan te passen"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Widgets aanpassen"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App-icoon voor uitgezette widget"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Widget bewerken"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Verwijderen"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Widget toevoegen"</string>
@@ -461,7 +462,10 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Widgets op het vergrendelscherm sluiten"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Widgets aanpassen"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets op het vergrendelscherm"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"widget selecteren"</string>
+    <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) -->
+    <skip />
+    <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) -->
     <skip />
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Gebruiker wijzigen"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pull-downmenu"</string>
@@ -617,7 +621,7 @@
     <string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"Ruimtelijke audio"</string>
     <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Uit"</string>
     <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Vast"</string>
-    <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Hoofd­beweging volgen"</string>
+    <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Hoofdtracking"</string>
     <string name="volume_ringer_change" msgid="3574969197796055532">"Tik om de beltoonmodus te wijzigen"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"geluid uit"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"geluid aanzetten"</string>
@@ -627,12 +631,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Uitvoerinstellingen invoeren"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volumeschuifregelaars uitgevouwen"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volumeschuifregelaars samengevouwen"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"geluid van %s uitzetten"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"geluid van %s aanzetten"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> wordt afgespeeld op"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio wordt afgespeeld op"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Bellen actief"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Systeem-UI-tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"Statusbalk"</string>
     <string name="demo_mode" msgid="263484519766901593">"Demomodus voor systeemgebruikersinterface"</string>
@@ -660,8 +669,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelliet, slechte verbinding"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelliet, goede verbinding"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelliet, verbinding beschikbaar"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS via satelliet"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Werkprofiel"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Leuk voor sommige gebruikers, maar niet voor iedereen"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Met Systeem-UI-tuner beschikt u over extra manieren om de Android-gebruikersinterface aan te passen. Deze experimentele functies kunnen veranderen, vastlopen of verdwijnen in toekomstige releases. Ga voorzichtig verder."</string>
@@ -866,8 +874,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Tegel toevoegen"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Verplaatsen naar <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Toevoegen aan positie <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Positie ongeldig."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Positie <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tegel toegevoegd"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tegel verwijderd"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index b39be4c..1444e6d 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ବାମ ସୀମାରେଖା <xliff:g id="PERCENT">%1$d</xliff:g> ଶତକଡ଼ା"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ଡାହାଣ ସୀମାରେଖା <xliff:g id="PERCENT">%1$d</xliff:g> ଶତକଡ଼ା"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"ୱାର୍କ ପ୍ରୋଫାଇଲରେ ଥିବା <xliff:g id="APP">%1$s</xliff:g>ରେ ସେଭ କରାଯାଇଛି"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"ପ୍ରାଇଭେଟ ପ୍ରୋଫାଇଲରେ ଥିବା <xliff:g id="APP">%1$s</xliff:g>ରେ ସେଭ କରାଯାଇଛି"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ଫାଇଲଗୁଡ଼ିକ"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ଏହି ସ୍କ୍ରିନସଟକୁ ଚିହ୍ନଟ କରିଛି।"</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> ଏବଂ ଅନ୍ୟ ଓପନ ଆପ୍ସ ଏହି ସ୍କ୍ରିନସଟକୁ ଚିହ୍ନଟ କରିଛି।"</string>
@@ -371,7 +370,7 @@
     <string name="quick_settings_contrast_high" msgid="656049259587494499">"ଅଧିକ"</string>
     <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"ହିଅରିଂ ଡିଭାଇସଗୁଡ଼ିକ"</string>
     <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"ହିଅରିଂ ଡିଭାଇସଗୁଡ଼ିକ"</string>
-    <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"ନୂଆ ଡିଭାଇସ ପେୟାର କର"</string>
+    <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"ନୂଆ ଡିଭାଇସ ପେୟାର କରନ୍ତୁ"</string>
     <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"ନୂଆ ଡିଭାଇସ ପେୟାର କରିବାକୁ କ୍ଲିକ କରନ୍ତୁ"</string>
     <string name="hearing_devices_presets_error" msgid="350363093458408536">"ପ୍ରିସେଟକୁ ଅପଡେଟ କରାଯାଇପାରିଲା ନାହିଁ"</string>
     <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ଡିଭାଇସର ମାଇକ୍ରୋଫୋନକୁ ଅନବ୍ଲକ କରିବେ?"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ୱିଜେଟଗୁଡ଼ିକୁ କଷ୍ଟମାଇଜ କରିବା ପାଇଁ ଅଧିକ ସମୟ ଦବାନ୍ତୁ"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ୱିଜେଟଗୁଡ଼ିକୁ କଷ୍ଟମାଇଜ କରନ୍ତୁ"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ଅକ୍ଷମ କରାଯାଇଥିବା ୱିଜେଟ ପାଇଁ ଆପ ଆଇକନ"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"ୱିଜେଟକୁ ଏଡିଟ କରନ୍ତୁ"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"କାଢ଼ି ଦିଅନ୍ତୁ"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ୱିଜେଟ ଯୋଗ କରନ୍ତୁ"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"ଲକ ସ୍କ୍ରିନରେ ଥିବା ୱିଜେଟଗୁଡ଼ିକୁ ବନ୍ଦ କରନ୍ତୁ"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ୱିଜେଟଗୁଡ଼ିକୁ କଷ୍ଟମାଇଜ କରନ୍ତୁ"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ଲକ ସ୍କ୍ରିନରେ ଥିବା ୱିଜେଟଗୁଡ଼ିକ"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ୱିଜେଟ ଚୟନ କରନ୍ତୁ"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ୱିଜେଟକୁ କାଢ଼ି ଦିଅନ୍ତୁ"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ଚୟନିତ ୱିଜେଟ ରଖନ୍ତୁ"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ୟୁଜର୍‍ ବଦଳାନ୍ତୁ"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ପୁଲଡାଉନ ମେନୁ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ଏହି ସେସନର ସମସ୍ତ ଆପ୍‌ ଓ ଡାଟା ଡିଲିଟ୍‌ ହୋଇଯିବ।"</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"ଆଉଟପୁଟ ସେଟିଂସ ଲେଖନ୍ତୁ"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ଭଲ୍ୟୁମ ସ୍ଲାଇଡରଗୁଡ଼ିକୁ ବିସ୍ତାର କରାଯାଇଛି"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ଭଲ୍ୟୁମ ସ୍ଲାଇଡରଗୁଡ଼ିକୁ ସଙ୍କୁଚିତ କରାଯାଇଛି"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%sକୁ ମ୍ୟୁଟ କରନ୍ତୁ"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%sକୁ ଅନମ୍ୟୁଟ କରନ୍ତୁ"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g>ରେ ପ୍ଲେ କରାଯାଉଛି"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"ଅଡିଓ ଏଥିରେ ପ୍ଲେ ହେବ"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"କଲ ଚାଲିଛି"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"ସିଷ୍ଟମ୍ UI ଟ୍ୟୁନର୍‍"</string>
     <string name="status_bar" msgid="4357390266055077437">"ଷ୍ଟାଟସ୍‍ ବାର୍‍"</string>
     <string name="demo_mode" msgid="263484519766901593">"ସିଷ୍ଟମ୍‌ UI ଡେମୋ ମୋଡ୍‌"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"ସାଟେଲାଇଟ, ଦୁର୍ବଳ କନେକ୍ସନ"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ସାଟେଲାଇଟ, ଭଲ କନେକ୍ସନ"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ସାଟେଲାଇଟ, କନେକ୍ସନ ଉପଲବ୍ଧ"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ସେଟେଲାଇଟ SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"ୱର୍କ ପ୍ରୋଫାଇଲ୍‌"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"କେତେକଙ୍କ ପାଇଁ ମଜାଦାର, କିନ୍ତୁ ସମସ୍ତଙ୍କ ପାଇଁ ନୁହେଁ"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Android ୟୁଜର୍‍ ଇଣ୍ଟରଫେସ୍‍ ବଦଳାଇବାକୁ ତଥା ନିଜ ପସନ୍ଦ ଅନୁଯାୟୀ କରିବାକୁ ସିଷ୍ଟମ୍‍ UI ଟ୍ୟୁନର୍‍ ଆପଣଙ୍କୁ ଅତିରିକ୍ତ ଉପାୟ ପ୍ରଦାନ କରେ। ଏହି ପରୀକ୍ଷାମୂଳକ ସୁବିଧାମାନ ବଦଳିପାରେ, ଭାଙ୍ଗିପାରେ କିମ୍ବା ଭବିଷ୍ୟତର ରିଲିଜ୍‌ଗୁଡ଼ିକରେ ନଦେଖାଯାଇପାରେ। ସତର୍କତାର ସହ ଆଗକୁ ବଢ଼ନ୍ତୁ।"</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"ଟାଇଲ୍ ଯୋଗ କରନ୍ତୁ"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g>କୁ ମୁଭ୍ କରନ୍ତୁ"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> ଅବସ୍ଥିତିରେ ଯୋଗ କରନ୍ତୁ"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ଅବସ୍ଥିତି ଅବୈଧ ଅଟେ।"</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ଅବସ୍ଥିତି <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ଟାଇଲ୍ ଯୋଗ କରାଯାଇଛି"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ଟାଇଲ୍ କାଢ଼ି ଦିଆଯାଇଛି"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 5efa1df..23914b3 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ਖੱਬੇ ਪਾਸੇ ਵਾਲੀ ਸੀਮਾ <xliff:g id="PERCENT">%1$d</xliff:g> ਫ਼ੀਸਦ"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ਸੱਜੇ ਪਾਸੇ ਵਾਲੀ ਸੀਮਾ <xliff:g id="PERCENT">%1$d</xliff:g> ਫ਼ੀਸਦ"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਵਿੱਚ <xliff:g id="APP">%1$s</xliff:g> ਵਿੱਚ ਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"<xliff:g id="APP">%1$s</xliff:g> ਦੇ ਨਿੱਜੀ ਪ੍ਰੋਫਾਈਲ ਵਿੱਚ ਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ਫ਼ਾਈਲਾਂ"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ਨੂੰ ਇਸ ਸਕ੍ਰੀਨਸ਼ਾਟ ਦਾ ਪਤਾ ਲੱਗਿਆ ਹੈ।"</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> ਅਤੇ ਹੋਰ ਖੁੱਲ੍ਹੀਆਂ ਐਪਾਂ ਨੂੰ ਇਸ ਸਕ੍ਰੀਨਸ਼ਾਟ ਦਾ ਪਤਾ ਲੱਗਿਆ ਹੈ।"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ਵਿਜੇਟਾਂ ਨੂੰ ਵਿਉਂਤਬੱਧ ਕਰਨ ਲਈ ਦਬਾਈ ਰੱਖੋ"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ਵਿਜੇਟ ਵਿਉਂਤਬੱਧ ਕਰੋ"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ਬੰਦ ਵਿਜੇਟ ਲਈ ਐਪ ਪ੍ਰਤੀਕ"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"ਵਿਜੇਟ ਦਾ ਸੰਪਾਦਨ ਕਰੋ"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ਹਟਾਓ"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ਵਿਜੇਟ ਸ਼ਾਮਲ ਕਰੋ"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"ਲਾਕ ਸਕ੍ਰੀਨ \'ਤੇ ਵਿਜੇਟ ਬੰਦ ਕਰੋ"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ਵਿਜੇਟ ਵਿਉਂਤਬੱਧ ਕਰੋ"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ਲਾਕ ਸਕ੍ਰੀਨ \'ਤੇ ਵਿਜੇਟ"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ਵਿਜੇਟ ਚੁਣੋ"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ਵਿਜੇਟ ਹਟਾਓ"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ਚੁਣੇ ਗਏ ਵਿਜੇਟ ਲਈ ਥਾਂ ਚੁਣੋ"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ਵਰਤੋਂਕਾਰ ਸਵਿੱਚ ਕਰੋ"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ਪੁੱਲਡਾਊਨ ਮੀਨੂ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ਇਸ ਸੈਸ਼ਨ ਵਿਚਲੀਆਂ ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਡਾਟੇ ਨੂੰ ਮਿਟਾ ਦਿੱਤਾ ਜਾਵੇਗਾ।"</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"ਆਊਟਪੁੱਟ ਸੈਟਿੰਗਾਂ ਦਾਖਲ ਕਰੋ"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ਅਵਾਜ਼ ਸਲਾਈਡਰਾਂ ਵਿਸਤਾਰ ਕੀਤਾ ਗਿਆ"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ਅਵਾਜ਼ ਸਲਾਈਡਰਾਂ ਨੂੰ ਸਮੇਟਿਆ ਗਿਆ"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s ਨੂੰ ਮਿਊਟ ਕਰੋ"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s ਨੂੰ ਅਣਮਿਊਟ ਕਰੋ"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> ਚਲਾਇਆ ਜਾ ਰਿਹਾ ਹੈ"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"ਆਡੀਓ ਇਸ \'ਤੇ ਚੱਲੇਗੀ"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"ਕਾਲ ਜਾਰੀ ਹੈ"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"System UI ਟਿਊਨਰ"</string>
     <string name="status_bar" msgid="4357390266055077437">"ਸਥਿਤੀ ਪੱਟੀ"</string>
     <string name="demo_mode" msgid="263484519766901593">"ਸਿਸਟਮ UI ਡੈਮੋ ਮੋਡ"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"ਸੈਟੇਲਾਈਟ, ਕਨੈਕਸ਼ਨ ਖਰਾਬ ਹੈ"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ਸੈਟੇਲਾਈਟ, ਕਨੈਕਸ਼ਨ ਵਧੀਆ ਹੈ"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ਸੈਟੇਲਾਈਟ, ਕਨੈਕਸ਼ਨ ਉਪਲਬਧ ਹੈ"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ਸੈਟੇਲਾਈਟ SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"ਕੁਝ ਵਾਸਤੇ ਤਾਂ ਮਜ਼ੇਦਾਰ ਹੈ ਲੇਕਿਨ ਸਾਰਿਆਂ ਵਾਸਤੇ ਨਹੀਂ"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"ਸਿਸਟਮ UI ਟਿਊਨਰ ਤੁਹਾਨੂੰ Android ਵਰਤੋਂਕਾਰ ਇੰਟਰਫ਼ੇਸ ਤਬਦੀਲ ਕਰਨ ਅਤੇ ਵਿਉਂਤਬੱਧ ਕਰਨ ਲਈ ਵਾਧੂ ਤਰੀਕੇ ਦਿੰਦਾ ਹੈ। ਇਹ ਪ੍ਰਯੋਗਾਤਮਿਕ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਭਵਿੱਖ ਦੀ ਰੀਲੀਜ਼ ਵਿੱਚ ਬਦਲ ਸਕਦੀਆਂ ਹਨ, ਟੁੱਟ ਸਕਦੀਆਂ ਹਨ, ਜਾਂ ਅਲੋਪ ਹੋ ਸਕਦੀਆਂ ਹਨ। ਸਾਵਧਾਨੀ ਨਾਲ ਅੱਗੇ ਵੱਧੋ।"</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"ਟਾਇਲ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> \'ਤੇ ਲਿਜਾਓ"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> ਸਥਾਨ \'ਤੇ ਸ਼ਾਮਲ ਕਰੋ"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ਮੌਜੂਦਾ ਥਾਂ ਅਵੈਧ ਹੈ।"</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ਸਥਾਨ <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ਟਾਇਲ ਨੂੰ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ਟਾਇਲ ਨੂੰ ਹਟਾ ਦਿੱਤਾ ਗਿਆ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 0aa3140..4609f08 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Przycięcie lewej krawędzi o <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Przycięcie prawej krawędzi o <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Zapisano w aplikacji <xliff:g id="APP">%1$s</xliff:g> w profilu służbowym"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Zapisano w aplikacji <xliff:g id="APP">%1$s</xliff:g> w profilu prywatnym"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Pliki"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"Aplikacja <xliff:g id="APPNAME">%1$s</xliff:g> wykryła ten zrzut ekranu."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"Aplikacja <xliff:g id="APPNAME">%1$s</xliff:g> i inne aplikacje wykryły ten zrzut ekranu."</string>
@@ -277,7 +276,7 @@
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"rozłącz"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktywuj"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatycznie włącz ponownie jutro"</string>
-    <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Funkcje takie jak szybkie udostępnianie czy Znajdź moje urządzenie korzystają z Bluetootha"</string>
+    <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Bluetootha używają funkcje takie jak szybkie udostępnianie czy Znajdź moje urządzenie"</string>
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth włączy się jutro rano"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"Udostępnianie dźwięku"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="8626191139359072540">"Udostępniam dźwięk"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Przytrzymaj, aby dostosować widżety"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Dostosuj widżety"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacji z wyłączonym widżetem"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Edytuj widżet"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Usuń"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodaj widżet"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Zamknij widżety na ekranie blokady"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Dostosuj widżety"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widżety na ekranie blokady"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"wybierz widżet"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"usuń widżet"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"umieść wybrany widżet"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Przełącz użytkownika"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Wszystkie aplikacje i dane w tej sesji zostaną usunięte."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Otwórz ustawienia sygnału wyjściowego"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Suwaki głośności są rozwinięte"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Suwaki głośności są zwinięte"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"wycisz: %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"wyłącz wyciszenie: %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Odtwarzam <xliff:g id="LABEL">%s</xliff:g> na"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Wyjścia dźwięku:"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Dzwonię na:"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Kalibrator System UI"</string>
     <string name="status_bar" msgid="4357390266055077437">"Pasek stanu"</string>
     <string name="demo_mode" msgid="263484519766901593">"Tryb demonstracyjny interfejsu"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelita – połączenie słabe"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelita – połączenie dobre"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelita – połączenie dostępne"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satelitarne połączenie alarmowe"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profil służbowy"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Dobra zabawa, ale nie dla każdego"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Kalibrator System UI udostępnia dodatkowe sposoby dostrajania i dostosowywania interfejsu Androida. Te eksperymentalne funkcje mogą się zmienić, popsuć lub zniknąć w przyszłych wersjach. Zachowaj ostrożność."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Dodaj kartę"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Przenieś do pozycji <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodaj w pozycji <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Nieprawidłowa pozycja."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozycja <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Dodano kartę"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Usunięto kartę"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 80e0d22c..425fa65 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Borda esquerda em <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Borda direita em <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Salva no app <xliff:g id="APP">%1$s</xliff:g> no perfil de trabalho"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Salva no app <xliff:g id="APP">%1$s</xliff:g> no perfil particular"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"O app <xliff:g id="APPNAME">%1$s</xliff:g> detectou essa captura de tela."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> e outros apps abertos detectaram essa captura de tela."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantenha pressionado para personalizar widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ícone do app para widget desativado"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Remover"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adicionar widget"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Fechar widgets na tela de bloqueio"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizar widgets"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets na tela de bloqueio"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"selecionar widget"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"remover widget"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"posicionar widget selecionado"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Trocar usuário"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu suspenso"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string>
@@ -594,7 +596,7 @@
     <string name="screen_pinning_exit" msgid="4553787518387346893">"App liberado"</string>
     <string name="stream_voice_call" msgid="7468348170702375660">"Ligar"</string>
     <string name="stream_system" msgid="7663148785370565134">"Sistema"</string>
-    <string name="stream_ring" msgid="7550670036738697526">"Tocar"</string>
+    <string name="stream_ring" msgid="7550670036738697526">"Toques"</string>
     <string name="stream_music" msgid="2188224742361847580">"Mídia"</string>
     <string name="stream_alarm" msgid="16058075093011694">"Alarme"</string>
     <string name="stream_notification" msgid="7930294049046243939">"Notificação"</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Inserir configurações de saída"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Controles deslizantes de volume abertos"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Controles deslizantes de volume fechados"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"desativar o som de %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"ativar o som de %s"</string>
-    <string name="media_output_label_title" msgid="872824698593182505">"Tocando <xliff:g id="LABEL">%s</xliff:g> em"</string>
-    <string name="media_output_title_without_playing" msgid="3825663683169305013">"O áudio vai tocar em"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
     <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
+    <string name="media_output_label_title" msgid="872824698593182505">"Tocando <xliff:g id="LABEL">%s</xliff:g> em"</string>
+    <string name="media_output_title_without_playing" msgid="3825663683169305013">"Onde o áudio vai tocar?"</string>
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Ligando"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Sintonizador System UI"</string>
     <string name="status_bar" msgid="4357390266055077437">"Barra de status"</string>
     <string name="demo_mode" msgid="263484519766901593">"Modo de demonstração da interface do sistema"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satélite, conexão ruim"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, conexão boa"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, conexão disponível"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS via satélite"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Perfil de trabalho"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Diversão para alguns, mas não para todos"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"O sintonizador System UI fornece maneiras adicionais de ajustar e personalizar a interface do usuário do Android. Esses recursos experimentais podem mudar, falhar ou desaparecer nas versões futuras. Prossiga com cuidado."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Adicionar bloco"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mover para <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adicionar à posição <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posição inválida."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posição <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Bloco adicionado"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Bloco removido"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index c21766d..4453133 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Limite esquerdo de <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Limite direito de <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Guardada na app <xliff:g id="APP">%1$s</xliff:g> no perfil de trabalho"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Guardada na app <xliff:g id="APP">%1$s</xliff:g> no perfil privado"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Ficheiros"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"A app <xliff:g id="APPNAME">%1$s</xliff:g> detetou esta captura de ecrã."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"A app <xliff:g id="APPNAME">%1$s</xliff:g> e outras apps abertas detetaram esta captura de ecrã."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantenha premido para personalizar os widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ícone da app do widget desativado"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Remover"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adicionar widget"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Fechar widgets no ecrã de bloqueio"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizar widgets"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets no ecrã de bloqueio"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"selecionar widget"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"remover widget"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"posicionar widget selecionado"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Mudar utilizador"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu pendente"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todas as apps e dados desta sessão serão eliminados."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Introduzir definições de saída"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Controlos de deslize do volume expandidos"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Controlos de deslize do volume reduzidos"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"desativar o som de %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"reativar o som de %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"A ouvir <xliff:g id="LABEL">%s</xliff:g> em:"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Áudio ouvido em:"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Chamada em curso"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Sintonizador da interface do sistema"</string>
     <string name="status_bar" msgid="4357390266055077437">"Barra de estado"</string>
     <string name="demo_mode" msgid="263484519766901593">"Modo de demonstração da IU do sistema"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satélite, ligação fraca"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, boa ligação"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, ligação disponível"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satélite SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Perfil de trabalho"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Diversão para alguns, mas não para todos"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"O Sintonizador da interface do sistema disponibiliza-lhe formas adicionais ajustar e personalizar a interface do utilizador do Android. Estas funcionalidades experimentais podem ser alteradas, deixar de funcionar ou desaparecer em versões futuras. Prossiga com cuidado."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Adicionar cartão"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mova para <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adicione à posição <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posição inválida."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posição <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Cartão adicionado"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Cartão removido"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 80e0d22c..425fa65 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Borda esquerda em <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Borda direita em <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Salva no app <xliff:g id="APP">%1$s</xliff:g> no perfil de trabalho"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Salva no app <xliff:g id="APP">%1$s</xliff:g> no perfil particular"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"O app <xliff:g id="APPNAME">%1$s</xliff:g> detectou essa captura de tela."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> e outros apps abertos detectaram essa captura de tela."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantenha pressionado para personalizar widgets"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ícone do app para widget desativado"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Remover"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adicionar widget"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Fechar widgets na tela de bloqueio"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizar widgets"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets na tela de bloqueio"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"selecionar widget"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"remover widget"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"posicionar widget selecionado"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Trocar usuário"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu suspenso"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string>
@@ -594,7 +596,7 @@
     <string name="screen_pinning_exit" msgid="4553787518387346893">"App liberado"</string>
     <string name="stream_voice_call" msgid="7468348170702375660">"Ligar"</string>
     <string name="stream_system" msgid="7663148785370565134">"Sistema"</string>
-    <string name="stream_ring" msgid="7550670036738697526">"Tocar"</string>
+    <string name="stream_ring" msgid="7550670036738697526">"Toques"</string>
     <string name="stream_music" msgid="2188224742361847580">"Mídia"</string>
     <string name="stream_alarm" msgid="16058075093011694">"Alarme"</string>
     <string name="stream_notification" msgid="7930294049046243939">"Notificação"</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Inserir configurações de saída"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Controles deslizantes de volume abertos"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Controles deslizantes de volume fechados"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"desativar o som de %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"ativar o som de %s"</string>
-    <string name="media_output_label_title" msgid="872824698593182505">"Tocando <xliff:g id="LABEL">%s</xliff:g> em"</string>
-    <string name="media_output_title_without_playing" msgid="3825663683169305013">"O áudio vai tocar em"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
     <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
+    <string name="media_output_label_title" msgid="872824698593182505">"Tocando <xliff:g id="LABEL">%s</xliff:g> em"</string>
+    <string name="media_output_title_without_playing" msgid="3825663683169305013">"Onde o áudio vai tocar?"</string>
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Ligando"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Sintonizador System UI"</string>
     <string name="status_bar" msgid="4357390266055077437">"Barra de status"</string>
     <string name="demo_mode" msgid="263484519766901593">"Modo de demonstração da interface do sistema"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satélite, conexão ruim"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, conexão boa"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, conexão disponível"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS via satélite"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Perfil de trabalho"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Diversão para alguns, mas não para todos"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"O sintonizador System UI fornece maneiras adicionais de ajustar e personalizar a interface do usuário do Android. Esses recursos experimentais podem mudar, falhar ou desaparecer nas versões futuras. Prossiga com cuidado."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Adicionar bloco"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mover para <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adicionar à posição <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posição inválida."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posição <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Bloco adicionado"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Bloco removido"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 651f71c..596349e 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Marginea stângă la <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Marginea dreaptă la <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Salvată în <xliff:g id="APP">%1$s</xliff:g> în profilul de serviciu"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Salvată în <xliff:g id="APP">%1$s</xliff:g> în profilul privat"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fișiere"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> a detectat această captură de ecran."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> și alte aplicații deschise au detectat această captură de ecran."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Apasă lung pentru a personaliza widgeturi"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizează widgeturile"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Pictograma aplicației pentru widgetul dezactivat"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Editează widgetul"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Elimină"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adaugă un widget"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Închide widgeturile de pe ecranul de blocare"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizează widgeturile"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgeturi pe ecranul de blocare"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"selectează un widget"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"elimină widgetul"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"plasează widgetul selectat"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Schimbă utilizatorul"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"meniu vertical"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toate aplicațiile și datele din această sesiune vor fi șterse."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Introdu setările de ieșire"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Glisoare de volum extinse"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Glisoare de volum restrânse"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"dezactivează sunetul pentru %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"activează sunetul pentru %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Se redă <xliff:g id="LABEL">%s</xliff:g> pe"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Conținutul audio se va reda pe"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Apel în curs pe"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"Bară de stare"</string>
     <string name="demo_mode" msgid="263484519766901593">"Mod demonstrativ pentru IU sistem"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelit, conexiune slabă"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, conexiune bună"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, conexiune disponibilă"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS prin satelit"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profil de serviciu"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Distractiv pentru unii, dar nu pentru toată lumea"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner oferă modalități suplimentare de a ajusta și a personaliza interfața de utilizare Android. Aceste funcții experimentale pot să se schimbe, să se blocheze sau să dispară din versiunile viitoare. Continuă cu prudență."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Adaugă un card"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mută pe poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adaugă pe poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Poziție nevalidă."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Cardul a fost adăugat"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Cardul a fost eliminat"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 01109e4..6fabeae 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Граница слева: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Граница справа: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Скриншот сохранен в рабочем профиле в приложении <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Сохранено в приложении \"<xliff:g id="APP">%1$s</xliff:g>\" в личном профиле"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Файлы"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"Приложение \"<xliff:g id="APPNAME">%1$s</xliff:g>\" обнаружило создание скриншота."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"Приложение \"<xliff:g id="APPNAME">%1$s</xliff:g>\" и другие запущенные продукты обнаружили создание скриншота."</string>
@@ -267,7 +266,7 @@
     <string name="quick_settings_dnd_label" msgid="7728690179108024338">"Не беспокоить"</string>
     <string name="quick_settings_bluetooth_label" msgid="7018763367142041481">"Bluetooth"</string>
     <string name="quick_settings_bluetooth_detail_empty_text" msgid="5760239584390514322">"Нет доступных сопряженных устройств"</string>
-    <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Нажмите, чтобы подключить или отключить устройство"</string>
+    <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Нажмите, чтобы подключить или отключить устройство."</string>
     <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Подключить устройство"</string>
     <string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Все"</string>
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Использовать"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Нажмите и удерживайте, чтобы настроить виджеты."</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Настроить виджеты"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Значок приложения для отключенного виджета"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Изменить виджет"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Удалить"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Добавить виджет"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Закрыть виджеты на заблокированном экране"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Настроить виджеты"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Виджеты на заблокированном экране"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"выбрать виджет"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"удалить виджет"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"разместить выбранный виджет"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Сменить пользователя."</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"раскрывающееся меню"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Все приложения и данные этого профиля будут удалены."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Перейти к настройкам вывода аудио"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Ползунки для регулировки громкости развернуты"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Ползунки для регулировки громкости свернуты"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"отключить звук: %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"включить звук: %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> – запущено здесь:"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Проигрывание аудио:"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Настройки вызова"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"Строка состояния"</string>
     <string name="demo_mode" msgid="263484519766901593">"Интерфейс системы: деморежим"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Спутниковая связь, плохое качество соединения"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Спутниковая связь, хорошее качество соединения"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Доступно соединение по спутниковой связи"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Спутниковый SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Рабочий профиль"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Внимание!"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner позволяет настраивать интерфейс устройства Android по вашему вкусу. В будущем эта экспериментальная функция может измениться, перестать работать или исчезнуть."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Добавить панель"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Переместить на позицию <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Добавить на позицию <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Недопустимое расположение."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Позиция <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Панель добавлена"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Панель удалена"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index d59b0b0..238e6c8 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -447,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"විජට් අභිරුචිකරණය කිරීමට දිගු ඔබන්න"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"විජට්ටු අභිරුචි කරන්න"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"අබල කළ විජට් සඳහා යෙදුම් නිරූපකය"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"විජට්ටු සංස්කරණ කරන්න"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ඉවත් කරන්න"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"විජට්ටුව එක් කරන්න"</string>
@@ -461,6 +463,8 @@
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"විජට්ටු අභිරුචි කරන්න"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"අගුළු තිරයෙහි විජට්"</string>
     <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"විජට්ටුව තෝරන්න"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"විජට්ටුව ඉවත් කරන්න"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"තෝරන ලද විජට්ටුව තබන්න"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"පරිශීලක මාරුව"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"නිපතන මෙනුව"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"මෙම සැසියේ සියළුම යෙදුම් සහ දත්ත මකාවී."</string>
@@ -625,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"ප්‍රතිදාන සැකසීම් ඇතුල් කරන්න"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"හඬ ස්ලයිඩර දිගහැර ඇත"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"හඬ ස්ලයිඩර හකුළා ඇත"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s නිහඬ කරන්න"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s නිහඬ නොකරන්න"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> වාදනය කරන්නේ"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"ශ්‍රව්‍ය වාදනය වනු ඇත්තේ"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"ඇමතීම"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"පද්ධති UI සුසරකය"</string>
     <string name="status_bar" msgid="4357390266055077437">"තත්ත්ව තීරුව"</string>
     <string name="demo_mode" msgid="263484519766901593">"පද්ධති UI ආදර්ශන ප්‍රකාරය"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 2680d08..825dba6 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"<xliff:g id="PERCENT">%1$d</xliff:g> %% ľavej hranice"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"<xliff:g id="PERCENT">%1$d</xliff:g> %% pravej hranice"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Uložená v aplikácii <xliff:g id="APP">%1$s</xliff:g> v pracovnom profile"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Uložené v aplikácii <xliff:g id="APP">%1$s</xliff:g> v súkromnom profile"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"Aplikácia <xliff:g id="APPNAME">%1$s</xliff:g> zaznamenala túto snímku obrazovky."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> a ďalšie otvorené aplikácie zaznamenali túto snímku obrazovky."</string>
@@ -270,7 +269,7 @@
     <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Klepnutím pripojíte alebo odpojíte zariadenie"</string>
     <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Spárovať nové zariadenie"</string>
     <string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Zobraziť všetko"</string>
-    <string name="turn_on_bluetooth" msgid="5681370462180289071">"Použiť Bluetooth"</string>
+    <string name="turn_on_bluetooth" msgid="5681370462180289071">"Používať Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Pripojené"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Zdieľanie zvuku"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Uložené"</string>
@@ -371,7 +370,7 @@
     <string name="quick_settings_contrast_high" msgid="656049259587494499">"Vysoký"</string>
     <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"Načúvacie zariadenia"</string>
     <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"Načúvacie zariadenia"</string>
-    <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Párovanie nového zariadenia"</string>
+    <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Spárovať nové zariadenie"</string>
     <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Kliknutím spárujete nové zariadenie"</string>
     <string name="hearing_devices_presets_error" msgid="350363093458408536">"Predvoľbu sa nepodarilo aktualizovať"</string>
     <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Chcete odblokovať mikrofón zariadenia?"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Miniaplikácie prispôsobíte dlhým stlačením"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prispôsobiť miniaplikácie"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona deaktivovanej miniaplikácie"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Upraviť miniaplikáciu"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Odstrániť"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Pridať miniaplikáciu"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Zavrieť miniaplikácie na uzamknutej obrazovke"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Prispôsobiť miniaplikácie"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Miniaplikácie na uzamknutej obrazovke"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"vybrať miniaplikáciu"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"odstrániť miniaplikáciu"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"prepnúť vybranú miniaplikáciu"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Prepnutie používateľa"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rozbaľovacia ponuka"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Všetky aplikácie a údaje v tejto relácii budú odstránené."</string>
@@ -592,7 +594,7 @@
     <string name="screen_pinning_negative" msgid="6882816864569211666">"Nie, vďaka"</string>
     <string name="screen_pinning_start" msgid="7483998671383371313">"Aplikácia bola pripnutá"</string>
     <string name="screen_pinning_exit" msgid="4553787518387346893">"Aplikácia bola odopnutá"</string>
-    <string name="stream_voice_call" msgid="7468348170702375660">"Zavolať"</string>
+    <string name="stream_voice_call" msgid="7468348170702375660">"Hovor"</string>
     <string name="stream_system" msgid="7663148785370565134">"Systém"</string>
     <string name="stream_ring" msgid="7550670036738697526">"Zvonenie"</string>
     <string name="stream_music" msgid="2188224742361847580">"Médiá"</string>
@@ -617,7 +619,7 @@
     <string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"Priestorový zvuk"</string>
     <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Vypnuté"</string>
     <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Pevné"</string>
-    <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Sled. pohybov hlavy"</string>
+    <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Sled. polohy hlavy"</string>
     <string name="volume_ringer_change" msgid="3574969197796055532">"Režim zvonenia zmeníte klepnutím"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"vypnite zvuk"</string>
     <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"zapnite zvuk"</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Zadať nastavenia výstupu"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Posúvače hlasitosti sú rozbalené"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Posúvače hlasitosti sú zbalené"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"vypnete zvuk %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"zapnete zvuk %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> sa prehráva v:"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Zvuk sa prehrá cez"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Volanie je zapnuté"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Tuner používateľského rozhrania systému"</string>
     <string name="status_bar" msgid="4357390266055077437">"Stavový riadok"</string>
     <string name="demo_mode" msgid="263484519766901593">"Ukážka používateľského rozhrania systému"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelit, slabá kvalita pripojenia"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, dobrá kvalita pripojenia"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, pripojenie je k dispozícii"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Pomoc cez satelit"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Pracovný profil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Pri používaní tuneru postupujte opatrne"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Tuner používateľského rozhrania systému poskytujte ďalšie spôsoby ladenia a prispôsobenia používateľského rozhrania Android. Tieto experimentálne funkcie sa môžu v budúcich verziách zmeniť, ich poskytovanie môže byť prerušené alebo môžu byť odstránené. Pokračujte opatrne."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Pridať kartu"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Presunúť na <xliff:g id="POSITION">%1$d</xliff:g>. pozíciu"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Pridať na <xliff:g id="POSITION">%1$d</xliff:g>. pozíciu"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Pozícia je neplatná."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>. pozícia"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Karta bola pridaná"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Karta bola odstránená"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 2e490a8..82f2df7 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Meja levo <xliff:g id="PERCENT">%1$d</xliff:g> odstotkov"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Meja desno <xliff:g id="PERCENT">%1$d</xliff:g> odstotkov"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Shranjeno v aplikaciji <xliff:g id="APP">%1$s</xliff:g> v delovnem profilu"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Shranjeno v aplikaciji <xliff:g id="APP">%1$s</xliff:g> v zasebnem profilu"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Datoteke"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"Aplikacija <xliff:g id="APPNAME">%1$s</xliff:g> je zaznala ta posnetek zaslona."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> in druge odprte aplikacije so zaznale ta posnetek zaslona."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pridržite za prilagajanje pripomočkov"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prilagajanje pripomočkov"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacije za onemogočen pripomoček"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Urejanje pripomočka"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Odstrani"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodajanje pripomočka"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Zapiranje pripomočkov na zaklenjenem zaslonu"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Prilagajanje pripomočkov"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Pripomočki na zaklenjenem zaslonu"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"izberite pripomoček"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"odstranitev pripomočka"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"postavitev izbranega pripomočka"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Preklop med uporabniki"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"spustni meni"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Vse aplikacije in podatki v tej seji bodo izbrisani."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Vnos izhodnih nastavitev"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Razširitev drsnikov za glasnost"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Strnitev drsnikov za glasnost"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"izklop zvoka %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"vklop zvoka %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Predvajanje »<xliff:g id="LABEL">%s</xliff:g>« v"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Zvok bo predvajan v"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Poteka klicanje"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Uglaševalnik uporabniškega vmesnika sistema"</string>
     <string name="status_bar" msgid="4357390266055077437">"Vrstica stanja"</string>
     <string name="demo_mode" msgid="263484519766901593">"Predstavitveni način uporabniškega vmesnika sistema"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelit, slaba povezava"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, dobra povezava"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, povezava je na voljo"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS prek satelita"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Delovni profil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Zabavno za nekatere, a ne za vse"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Uglaševalnik uporabniškega vmesnika sistema vam omogoča dodatne načine za spreminjanje in prilagajanje uporabniškega vmesnika Android. Te poskusne funkcije lahko v prihodnjih izdajah kadar koli izginejo, se spremenijo ali pokvarijo. Bodite previdni."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Dodajanje ploščice"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Premik na položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodajanje na položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Položaj je neveljaven."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Ploščica je bila dodana"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Ploščica je bila odstranjena"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 5e15bf0..af2e3c7 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Kufiri i majtë <xliff:g id="PERCENT">%1$d</xliff:g> për qind"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Kufiri i djathtë <xliff:g id="PERCENT">%1$d</xliff:g> për qind"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Ruajtur në <xliff:g id="APP">%1$s</xliff:g> në profilin e punës"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Ruajtur në <xliff:g id="APP">%1$s</xliff:g> në profilin privat"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Skedarë"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> zbuloi këtë pamje ekrani."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> dhe aplikacionet e tjera të hapura zbuluan këtë pamje ekrani."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Shtyp gjatë për të personalizuar miniaplikacionet"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizo miniaplikacionet"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona e aplikacionit për miniaplikacionin e çaktivizuar"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Modifiko miniaplikacionin"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Hiq"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Shto miniaplikacionin"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Mbyll miniaplikacionet në ekranin e kyçjes"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizo miniaplikacionet"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Miniaplikacionet në ekranin e kyçjes"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"zgjidh miniaplikacionin"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"hiq miniaplikacionin"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"vendos miniaplikacionin e zgjedhur"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Ndërro përdorues"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menyja me tërheqje poshtë"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Të gjitha aplikacionet dhe të dhënat në këtë sesion do të fshihen."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Hyr te cilësimet e daljes"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Rrëshqitësit e volumit u zgjeruan"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Rrëshqitësit e volumit u palosën"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"çaktivizo audion: %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"aktivizo audion: %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Po luhet <xliff:g id="LABEL">%s</xliff:g> te"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Do të luhet audio te"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Telefonatë aktive"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Sintonizuesi i Sistemit të Ndërfaqes së Përdoruesit"</string>
     <string name="status_bar" msgid="4357390266055077437">"Shiriti i statusit"</string>
     <string name="demo_mode" msgid="263484519766901593">"Modaliteti i demonstrimit i ndërfaqes së përdoruesit të sistemit"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Sateliti. Lidhje e dobët"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Sateliti. Lidhje e mirë"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Sateliti. Ofrohet lidhje"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS satelitor"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profili i punës"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Argëtim për disa, por jo për të gjithë!"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Sintonizuesi i Sistemit të Ndërfaqes së Përdoruesit të jep mënyra shtesë për të tërhequr dhe personalizuar ndërfaqen Android të përdoruesit. Këto funksione eksperimentale mund të ndryshojnë, prishen ose zhduken në versionet e ardhshme. Vazhdo me kujdes."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Shto pllakëzën"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Zhvendos te <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Shto te pozicioni <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Pozicion i pavlefshëm."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozicioni <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Pllakëza u shtua"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Pllakëza u hoq"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 8fa9fc3..42b979c 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Лева ивица <xliff:g id="PERCENT">%1$d</xliff:g> посто"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Десна ивица <xliff:g id="PERCENT">%1$d</xliff:g> посто"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Сачувано је у апликацији <xliff:g id="APP">%1$s</xliff:g> на пословном профилу"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Сачувано је у апликацији <xliff:g id="APP">%1$s</xliff:g> на приватном профилу"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Фајлови"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"Апликација <xliff:g id="APPNAME">%1$s</xliff:g> је открила овај снимак екрана."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> и друге отворене апликације су откриле овај снимак екрана."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Дуги притисак за прилагођавање виџета"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Прилагоди виџете"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Икона апликације за онемогућен виџет"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Измени виџет"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Уклони"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Додај виџет"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Затворите виџете на закључаном екрану"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Прилагодите виџете"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Виџети на закључаном екрану"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"изаберите виџет"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"уклоните виџет"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"поставите изабрани виџет"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Замени корисника"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"падајући мени"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Све апликације и подаци у овој сесији ће бити избрисани."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Унесите подешавања излазног сигнала"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Клизачи за јачину звука су проширени"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Клизачи за јачину звука су скупљени"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"искључите звук за: %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"укључите звук за: %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> се пушта на"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Звук се пушта на"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Позив на уређају"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Тјунер за кориснички интерфејс система"</string>
     <string name="status_bar" msgid="4357390266055077437">"Статусна трака"</string>
     <string name="demo_mode" msgid="263484519766901593">"Режим демонстрације за кориснички интерфејс система"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Сателит, веза је лоша"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Сателит, веза је добра"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Сателит, веза је доступна"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Хитна помоћ преко сателита"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Пословни профил"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Забава за неке, али не за све"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Тјунер за кориснички интерфејс система вам пружа додатне начине за подешавање и прилагођавање Android корисничког интерфејса. Ове експерименталне функције могу да се промене, откажу или нестану у будућим издањима. Будите опрезни."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Додајте плочицу"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Преместите на <xliff:g id="POSITION">%1$d</xliff:g>. позицију"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Додајте на <xliff:g id="POSITION">%1$d</xliff:g>. позицију"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Позиција је неважећа."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>. позиција"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Плочица је додата"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Плочица је уклоњена"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index c3b6f1e..29dca46 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Vänster gräns: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Höger gräns: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Sparad i <xliff:g id="APP">%1$s</xliff:g> i jobbprofilen"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Sparad i <xliff:g id="APP">%1$s</xliff:g> i den privata profilen"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Filer"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> identifierade skärmbilden."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> och andra öppna appar identifierade skärmbilden."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Tryck länge för att anpassa widgetar"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Anpassa widgetar"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Appikon för inaktiverad widget"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Redigera widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Ta bort"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Lägg till widget"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Stäng widgetar på låsskärmen"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Anpassa widgetar"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgetar på låsskärmen"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"välj widget"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ta bort widget"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"placera vald widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Byt användare"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullgardinsmeny"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alla appar och data i denna session kommer att raderas."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Ange inställningar för utdata"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volymreglagen har utökats"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volymreglagen har komprimerats"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"stäng av ljudet för %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"slå på ljudet för %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Spelar upp <xliff:g id="LABEL">%s</xliff:g> på"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Ljud spelas upp på"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Samtal på"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Inställningar för systemgränssnitt"</string>
     <string name="status_bar" msgid="4357390266055077437">"Statusfält"</string>
     <string name="demo_mode" msgid="263484519766901593">"Demoläge för systemgränssnitt"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satellit, dålig anslutning"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellit, bra anslutning"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellit, anslutning tillgänglig"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS-larm via satellit"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Jobbprofil"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Kul för vissa, inte för alla"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Du kan använda inställningarna för systemgränssnitt för att justera användargränssnittet i Android. Dessa experimentfunktioner kan när som helst ändras, sluta fungera eller försvinna. Använd med försiktighet."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Lägg till ruta"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Flytta till <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Lägg till på position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Positionen är ogiltig."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kortet har lagts till"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kortet har tagits bort"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index ffcf356..9d5181e 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Mpaka wa sehemu ya kushoto wa asilimia <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Mpaka wa sehemu ya kulia wa asilimia <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Imehifadhiwa kwenye <xliff:g id="APP">%1$s</xliff:g> katika wasifu wa kazini"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Imehifadhiwa kwenye <xliff:g id="APP">%1$s</xliff:g> katika wasifu binafsi"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Faili"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> imetambua picha hii ya skrini."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> na zingine zinazotumika zimetambua picha hii ya skrini."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Bonyeza kwa muda mrefu uweke mapendeleo ya wijeti"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Badilisha wijeti upendavyo"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Aikoni ya programu ya wijeti iliyozimwa"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Badilisha wijeti"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Ondoa"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ongeza wijeti"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Funga wijeti kwenye skrini iliyofungwa"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Badilisha wijeti upendavyo"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Wijeti kwenye skrini iliyofungwa"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"chagua wijeti"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ondoa wijeti"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"weka wijeti uliyochagua"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Badili mtumiaji"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menyu ya kuvuta chini"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Data na programu zote katika kipindi hiki zitafutwa."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Weka mipangilio ya sauti inayotoka"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Vitelezi vya sauti vimepanuliwa"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Vitelezi vya kiwango cha sauti vimekunjwa"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"zima sauti ya %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"rejesha sauti ya %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Inacheza <xliff:g id="LABEL">%s</xliff:g> kwenye"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Sauti itacheza kwenye"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Simu inaendelea"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Kirekebishi cha kiolesura cha mfumo"</string>
     <string name="status_bar" msgid="4357390266055077437">"Sehemu ya kuonyesha hali"</string>
     <string name="demo_mode" msgid="263484519766901593">"Hali ya onyesho la kirekebishi cha kiolesura cha mfumo"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Setilaiti, muunganisho hafifu"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Setilaiti, muunganisho thabiti"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Setilaiti, muunganisho unapatikana"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Msaada kupitia Setilaiti"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Wasifu wa kazini"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Kinafurahisha kwa baadhi ya watu lakini si wote"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Kirekebishi cha kiolesura cha mfumo kinakupa njia zaidi za kugeuza na kubadilisha kiolesura cha Android ili kikufae. Vipengele hivi vya majaribio vinaweza kubadilika, kuharibika au kupotea katika matoleo ya siku zijazo. Endelea kwa uangalifu."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Ongeza kigae"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Hamishia kwenye <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Ongeza kwenye nafasi ya <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Nafasi si sahihi."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Nafasi ya <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kigae kimewekwa"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kigae kimeondolewa"</string>
diff --git a/packages/SystemUI/res/values-sw600dp-land/styles.xml b/packages/SystemUI/res/values-sw600dp-land/styles.xml
index 0d46cbc..cde1a1373 100644
--- a/packages/SystemUI/res/values-sw600dp-land/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/styles.xml
@@ -18,7 +18,7 @@
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
 
     <style name="TextAppearance.AuthNonBioCredential.Title">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">16dp</item>
         <item name="android:textSize">36sp</item>
         <item name="android:focusable">true</item>
@@ -26,14 +26,14 @@
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Subtitle">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">16dp</item>
         <item name="android:textSize">18sp</item>
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Description">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">16dp</item>
         <item name="android:textSize">18sp</item>
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
diff --git a/packages/SystemUI/res/values-sw600dp-port/styles.xml b/packages/SystemUI/res/values-sw600dp-port/styles.xml
index 3add566..85e7af6 100644
--- a/packages/SystemUI/res/values-sw600dp-port/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/styles.xml
@@ -26,7 +26,7 @@
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Title">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">24dp</item>
         <item name="android:textSize">36sp</item>
         <item name="android:focusable">true</item>
diff --git a/packages/SystemUI/res/values-sw720dp-land/styles.xml b/packages/SystemUI/res/values-sw720dp-land/styles.xml
index 7cdd07b..e75173d 100644
--- a/packages/SystemUI/res/values-sw720dp-land/styles.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/styles.xml
@@ -18,7 +18,7 @@
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
 
     <style name="TextAppearance.AuthNonBioCredential.Title">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">16dp</item>
         <item name="android:textSize">36sp</item>
         <item name="android:focusable">true</item>
@@ -26,14 +26,14 @@
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Subtitle">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">16dp</item>
         <item name="android:textSize">18sp</item>
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Description">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">16dp</item>
         <item name="android:textSize">18sp</item>
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
diff --git a/packages/SystemUI/res/values-sw720dp-port/styles.xml b/packages/SystemUI/res/values-sw720dp-port/styles.xml
index 3add566..85e7af6 100644
--- a/packages/SystemUI/res/values-sw720dp-port/styles.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/styles.xml
@@ -26,7 +26,7 @@
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Title">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">24dp</item>
         <item name="android:textSize">36sp</item>
         <item name="android:focusable">true</item>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index e19f66e..a2f4ce5 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"இடது எல்லை <xliff:g id="PERCENT">%1$d</xliff:g> சதவீதம்"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"வலது எல்லை <xliff:g id="PERCENT">%1$d</xliff:g> சதவீதம்"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"பணிக் கணக்கில் உள்ள <xliff:g id="APP">%1$s</xliff:g> ஆப்ஸில் சேமிக்கப்பட்டது"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"தனிப்பட்ட சுயவிவரத்தில் உள்ள <xliff:g id="APP">%1$s</xliff:g> ஆப்ஸில் சேமிக்கப்பட்டது"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"இந்த ஸ்கிரீன்ஷாட்டை <xliff:g id="APPNAME">%1$s</xliff:g> கண்டறிந்துள்ளது."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"இந்த ஸ்கிரீன்ஷாட்டை <xliff:g id="APPNAME">%1$s</xliff:g> மற்றும் திறந்திருக்கும் பிற ஆப்ஸ் கண்டறிந்துள்ளன."</string>
@@ -276,7 +275,7 @@
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"சேமிக்கப்பட்டது"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"இணைப்பு நீக்கும்"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"செயல்படுத்தும்"</string>
-    <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"நாளைக்குத் தானாகவே மீண்டும் இயக்கப்படும்"</string>
+    <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"நாளைக்குத் தானாகவே மீண்டும் இயக்கப்படுதல்"</string>
     <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"விரைவுப் பகிர்தல், Find My Device போன்ற அம்சங்கள் புளூடூத்தைப் பயன்படுத்துகின்றன"</string>
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"நாளை காலை புளூடூத் இயக்கப்படும்"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"ஆடியோ பகிர்வு"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"விட்ஜெட்களைப் பிரத்தியேகமாக்க நீண்ட நேரம் அழுத்துக"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"விட்ஜெட்களைப் பிரத்தியேகமாக்குங்கள்"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"முடக்கப்பட்ட விட்ஜெட்டுக்கான ஆப்ஸ் ஐகான்"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"விட்ஜெட்டைத் திருத்து"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"அகற்றும்"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"விட்ஜெட்டைச் சேர்"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"பூட்டுத் திரையில் விட்ஜெட்களை மூடும்"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"விட்ஜெட்களைப் பிரத்தியேகமாக்கும்"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"பூட்டுத் திரையில் விட்ஜெட்கள்"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"விட்ஜெட்டைத் தேர்ந்தெடுக்கும்"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"விட்ஜெட்டை அகற்றும்"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"தேர்ந்தெடுத்த விட்ஜெட்டைக் காட்சிப்படுத்தும்"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"பயனரை மாற்று"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"கீழ் இழுக்கும் மெனு"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"இந்த அமர்வின் எல்லா ஆப்ஸும் தரவும் நீக்கப்படும்."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"வெளியீட்டு அமைப்புகளுக்குச் செல்லும்"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ஒலியளவு ஸ்லைடர்கள் விரிவாக்கப்பட்டன"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ஒலியளவு ஸ்லைடர்கள் சுருக்கப்பட்டன"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s ஐ ஒலியடக்கும்"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s ஐ ஒலி இயக்கும்"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"இதில் <xliff:g id="LABEL">%s</xliff:g> பிளே ஆகிறது"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"ஆடியோ இதில் பிளே ஆகும்"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"அழைப்பில் உள்ளது"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"நிலைப் பட்டி"</string>
     <string name="demo_mode" msgid="263484519766901593">"சிஸ்டம் பயனர் இடைமுக டெமோ பயன்முறை"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"சாட்டிலைட், மோசமான இணைப்பு"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"சாட்டிலைட், நிலையான இணைப்பு"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"சாட்டிலைட், இணைப்பு கிடைக்கிறது"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"சாட்டிலைட் SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"பணிக் கணக்கு"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"சில வேடிக்கையாக இருந்தாலும் கவனம் தேவை"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner, Android பயனர் இடைமுகத்தை மாற்றவும் தனிப்பயனாக்கவும் கூடுதல் வழிகளை வழங்குகிறது. இந்தப் பரிசோதனைக்குரிய அம்சங்கள் எதிர்கால வெளியீடுகளில் மாற்றப்படலாம், இடைநிறுத்தப்படலாம் அல்லது தோன்றாமல் போகலாம். கவனத்துடன் தொடரவும்."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"கட்டத்தைச் சேர்"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g>க்கு நகர்த்தும்"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g>ல் சேர்க்கும்"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"நிலை தவறானது."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"இடம்: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"கட்டம் சேர்க்கப்பட்டது"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"கட்டம் அகற்றப்பட்டது"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 0d079bf..5b703a0 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ఎడమ వైపు సరిహద్దు <xliff:g id="PERCENT">%1$d</xliff:g> శాతం"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"కుడి వైపు సరిహద్దు <xliff:g id="PERCENT">%1$d</xliff:g> శాతం"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"వర్క్ ప్రొఫైల్‌లోని <xliff:g id="APP">%1$s</xliff:g>‌లో సేవ్ చేయబడింది"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"ప్రైవేట్ ప్రొఫైల్‌లోని <xliff:g id="APP">%1$s</xliff:g>‌లో సేవ్ చేయబడింది"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ఫైల్స్"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g>, ఈ స్క్రీన్‌షాట్‌ను గుర్తించింది."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g>, ఇతర ఓపెన్ యాప్‌లు ఈ స్క్రీన్‌షాట్‌ను గుర్తించాయి."</string>
@@ -273,7 +272,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"బ్లూటూత్ వాడండి"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"కనెక్ట్ అయింది"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ఆడియో షేరింగ్"</string>
-    <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"సేవ్ చేయబడింది"</string>
+    <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"సేవ్ అయ్యింది"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"డిస్‌కనెక్ట్ చేయండి"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"యాక్టివేట్ చేయండి"</string>
     <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"రేపు మళ్లీ ఆటోమేటిక్‌గా ఆన్ చేస్తుంది"</string>
@@ -370,7 +369,7 @@
     <string name="quick_settings_contrast_medium" msgid="5158352575583902566">"మధ్యస్థం"</string>
     <string name="quick_settings_contrast_high" msgid="656049259587494499">"అధికం"</string>
     <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"వినికిడి పరికరాలు"</string>
-    <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"వినికిడి పరికరం"</string>
+    <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"వినికిడి పరికరాలు"</string>
     <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"కొత్త పరికరాన్ని పెయిర్ చేయండి"</string>
     <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"కొత్త పరికరాన్ని పెయిర్ చేయడానికి క్లిక్ చేయండి"</string>
     <string name="hearing_devices_presets_error" msgid="350363093458408536">"ప్రీసెట్‌ను అప్‌డేట్ చేయడం సాధ్యపడలేదు"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"విడ్జెట్‌లను అనుకూలీకరించడానికి, నొక్కి, ఉంచండి"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"విడ్జెట్‌లను అనుకూలంగా మార్చండి"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"డిజేబుల్ చేయబడిన విడ్జెట్ కోసం యాప్ చిహ్నం"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"విడ్జెట్‌ను ఎడిట్ చేయండి"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"తీసివేయండి"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"విడ్జెట్‌ను జోడించండి"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"లాక్ స్క్రీన్‌లో విడ్జెట్‌లను మూసివేయండి"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"విడ్జెట్‌లను అనుకూలంగా మార్చండి"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"లాక్ స్క్రీన్‌లో విడ్జెట్‌లు"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"విడ్జెట్‌ను ఎంచుకోండి"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"విడ్జెట్‌ను తీసివేయండి"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ఎంచుకున్న విడ్జెట్ కోసం ప్లేస్‌ను ఎంచుకోండి"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"వినియోగదారుని మార్చు"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"పుల్‌డౌన్ మెనూ"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ఈ సెషన్‌లోని అన్ని యాప్‌లు మరియు డేటా తొలగించబడతాయి."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"అవుట్‌పుట్ సెట్టింగ్‌లను ఎంటర్ చేయండి"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"వాల్యూమ్ స్లయిడర్‌లు విస్తరించబడ్డాయి"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"వాల్యూమ్ స్లయిడర్‌లు కుదించబడ్డాయి"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%sను మ్యూట్ చేయండి"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%sను అన్‌మ్యూట్ చేయండి"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g>‌ ప్లే అయ్యే డివైజ్"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"ఆడియో ప్లే డివైజ్"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"కాల్ ప్రోగ్రెస్‌లో ఉంది"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"సిస్టమ్ UI ట్యూనర్"</string>
     <string name="status_bar" msgid="4357390266055077437">"స్టేటస్‌ బార్‌"</string>
     <string name="demo_mode" msgid="263484519766901593">"సిస్టమ్ UI డెమో మోడ్"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"శాటిలైట్, కనెక్షన్ సరిగా లేదు"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"శాటిలైట్, కనెక్షన్ బాగుంది"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"శాటిలైట్, కనెక్షన్ అందుబాటులో ఉంది"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ఎమర్జెన్సీ శాటిలైట్ సహాయం"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"ఆఫీస్ ప్రొఫైల్‌"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"కొందరికి సరదాగా ఉంటుంది కానీ అందరికీ అలాగే ఉండదు"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"సిస్టమ్ UI ట్యూనర్ Android వినియోగదారు ఇంటర్‌ఫేస్‌ను మెరుగుపరచడానికి మరియు అనుకూలంగా మార్చడానికి మీకు మరిన్ని మార్గాలను అందిస్తుంది. ఈ ప్రయోగాత్మక లక్షణాలు భవిష్యత్తు విడుదలల్లో మార్పుకు లోనవ్వచ్చు, తాత్కాలికంగా లేదా పూర్తిగా నిలిపివేయవచ్చు. జాగ్రత్తగా కొనసాగండి."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"టైల్‌ను జోడించండి"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g>కు తరలించండి"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> స్థానానికి జోడించండి"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ప్రస్తుత పొజిషన్ చెల్లదు."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"స్థానం <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"టైల్ జోడించబడింది"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"టైల్ తీసివేయబడింది"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 7063592..73ca200 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ขอบเขตด้านซ้าย <xliff:g id="PERCENT">%1$d</xliff:g> เปอร์เซ็นต์"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ขอบเขตด้านขวา <xliff:g id="PERCENT">%1$d</xliff:g> เปอร์เซ็นต์"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"บันทึกไว้ที่ <xliff:g id="APP">%1$s</xliff:g> ในโปรไฟล์งาน"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"บันทึกไว้ที่ <xliff:g id="APP">%1$s</xliff:g> ในโปรไฟล์ส่วนตัว"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ไฟล์"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ตรวจพบภาพหน้าจอนี้"</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> และแอปอื่นๆ ที่เปิดอยู่ตรวจพบภาพหน้าจอนี้"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"กดค้างเพื่อปรับแต่งวิดเจ็ต"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ปรับแต่งวิดเจ็ต"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ไอคอนแอปสำหรับวิดเจ็ตที่ปิดใช้อยู่"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"แก้ไขวิดเจ็ต"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"นำออก"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"เพิ่มวิดเจ็ต"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"ปิดวิดเจ็ตในหน้าจอล็อก"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ปรับแต่งวิดเจ็ต"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"วิดเจ็ตในหน้าจอล็อก"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"เลือกวิดเจ็ต"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"นำวิดเจ็ตออก"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"จัดวางวิดเจ็ตที่เลือก"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"สลับผู้ใช้"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"เมนูแบบเลื่อนลง"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ระบบจะลบแอปและข้อมูลทั้งหมดในเซสชันนี้"</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"เข้าสู่การตั้งค่าเอาต์พุต"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ขยายแถบเลื่อนระดับเสียงแล้ว"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ยุบแถบเลื่อนระดับเสียงแล้ว"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"ปิดเสียง%s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"เปิดเสียง%s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"กำลังเล่น <xliff:g id="LABEL">%s</xliff:g> ใน"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"เสียงจะเล่นต่อใน"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"กำลังโทรติดต่อ"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"ตัวรับสัญญาณ UI ระบบ"</string>
     <string name="status_bar" msgid="4357390266055077437">"แถบสถานะ"</string>
     <string name="demo_mode" msgid="263484519766901593">"โหมดสาธิต UI ของระบบ"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"ดาวเทียม, การเชื่อมต่อไม่ดี"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ดาวเทียม, การเชื่อมต่อดี"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ดาวเทียม, การเชื่อมต่อที่พร้อมใช้งาน"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS ดาวเทียม"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"โปรไฟล์งาน"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"เพลิดเพลินกับบางส่วนแต่ไม่ใช่ทั้งหมด"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"ตัวรับสัญญาณ UI ระบบช่วยให้คุณมีวิธีพิเศษในการปรับแต่งและกำหนดค่าส่วนติดต่อผู้ใช้ Android ฟีเจอร์รุ่นทดลองเหล่านี้อาจมีการเปลี่ยนแปลง ขัดข้อง หรือหายไปในเวอร์ชันอนาคต โปรดดำเนินการด้วยความระมัดระวัง"</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"เพิ่มชิ้นส่วน"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"ย้ายไปที่ <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"เพิ่มไปยังตำแหน่ง <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ตำแหน่งไม่ถูกต้อง"</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ตำแหน่ง <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"เพิ่มชิ้นส่วนแล้ว"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"นำชิ้นส่วนออกแล้ว"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index fe18814..0c616fa 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"<xliff:g id="PERCENT">%1$d</xliff:g> (na) porsyento sa hangganan sa kaliwa"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"<xliff:g id="PERCENT">%1$d</xliff:g> (na) porsyento sa hangganan sa kanan"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Na-save sa <xliff:g id="APP">%1$s</xliff:g> sa profile sa trabaho"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Naka-save sa <xliff:g id="APP">%1$s</xliff:g> sa pribadong profile"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Mga File"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"Na-detect ng <xliff:g id="APPNAME">%1$s</xliff:g> ang screenshot. na ito"</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"Na-detect ng <xliff:g id="APPNAME">%1$s</xliff:g> at ng iba pang bukas na app ang screenshot na ito."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pindutin nang matagal para i-customize ang mga widget"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"I-customize ang mga widget"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icon ng app para sa na-disable na widget"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"I-edit ang widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Alisin"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Magdagdag ng widget"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Isara ang mga widget sa lock screen"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"I-customize ang mga widget"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Mga widget sa lock screen"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"pumili ng widget"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"alisin ang widget"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ilagay ang napiling widget"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Magpalit ng user"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ide-delete ang lahat ng app at data sa session na ito."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Pumunta sa mga setting ng output"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Na-expand ang mga slider ng volume"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Na-collapse ang mga slider ng volume"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"i-mute ang %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"i-unmute ang %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Nagpe-play ang <xliff:g id="LABEL">%s</xliff:g> sa"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"I-play ang audio sa"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Tumatawag sa"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Tuner ng System UI"</string>
     <string name="status_bar" msgid="4357390266055077437">"Status bar"</string>
     <string name="demo_mode" msgid="263484519766901593">"Demo mode ng System UI"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satellite, mahina ang koneksyon"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellite, malakas ang koneksyon"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellite, may koneksyon"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profile sa trabaho"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Masaya para sa ilan ngunit hindi para sa lahat"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Nagbibigay sa iyo ang Tuner ng System UI ng mga karagdagang paraan upang baguhin at i-customize ang user interface ng Android. Ang mga pang-eksperimentong feature na ito ay maaaring magbago, masira o mawala sa mga pagpapalabas sa hinaharap. Magpatuloy nang may pag-iingat."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Magdagdag ng tile"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Ilipat sa <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Idagdag sa posisyong <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Invalid ang posisyon."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posisyon <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Idinagdag ang tile"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Inalis ang tile"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index e0ea27d..6723c42 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Sol sınır yüzde <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Sağ sınır yüzde <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"İş profilinde <xliff:g id="APP">%1$s</xliff:g> uygulamasına kaydedildi"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Gizli profilde <xliff:g id="APP">%1$s</xliff:g> uygulamasına kaydedildi"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Dosyalar"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> bu ekran görüntüsünü algıladı."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> ve diğer açık uygulamalar bu ekran görüntüsünü algıladı."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Widget\'ları özelleştirmek için uzun basın"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Widget\'ları özelleştir"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Devre dışı bırakılan widget\'ın uygulama simgesi"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Widget\'ı düzenle"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Kaldır"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Widget ekle"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Kilit ekranındaki widget\'ları kapat"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Widget\'ları özelleştir"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Kilit ekranındaki widget\'lar"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"widget seçin"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"widget\'ı kaldır"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"seçilen widget\'ı yerleştir"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Kullanıcı değiştirme"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"açılır menü"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu oturumdaki tüm uygulamalar ve veriler silinecek."</string>
@@ -594,14 +596,14 @@
     <string name="screen_pinning_exit" msgid="4553787518387346893">"Uygulamanın sabitlemesi kaldırıldı"</string>
     <string name="stream_voice_call" msgid="7468348170702375660">"Çağrı"</string>
     <string name="stream_system" msgid="7663148785370565134">"Sistem"</string>
-    <string name="stream_ring" msgid="7550670036738697526">"Zili çaldır"</string>
+    <string name="stream_ring" msgid="7550670036738697526">"Zil"</string>
     <string name="stream_music" msgid="2188224742361847580">"Medya"</string>
     <string name="stream_alarm" msgid="16058075093011694">"Alarm"</string>
     <string name="stream_notification" msgid="7930294049046243939">"Bildirim"</string>
     <string name="stream_bluetooth_sco" msgid="6234562365528664331">"Bluetooth"</string>
     <string name="stream_dtmf" msgid="7322536356554673067">"Çift ton çoklu frekans"</string>
     <string name="stream_accessibility" msgid="3873610336741987152">"Erişilebilirlik"</string>
-    <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Zili çaldır"</string>
+    <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Zil"</string>
     <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Titreşim"</string>
     <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Sesi kapat"</string>
     <string name="media_device_cast" msgid="4786241789687569892">"Yayınla"</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Çıkış ayarlarını gir"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Ses seviyesi kaydırma çubukları genişletildi"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Ses seviyesi kaydırma çubukları daraltıldı"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s sesini kapatma"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s sesini açma"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> şurada çalacak:"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Ses şurada çalacak:"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Şu cihaz aranıyor:"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Sistem Arayüzü Ayarlayıcısı"</string>
     <string name="status_bar" msgid="4357390266055077437">"Durum çubuğu"</string>
     <string name="demo_mode" msgid="263484519766901593">"Sistem kullanıcı arayüzü demo modu"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Uydu, bağlantı zayıf"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Uydu, bağlantı güçlü"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Uydu, bağlantı mevcut"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Acil Uydu Bağlantısı"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"İş profili"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Bazıları için eğlenceliyken diğerleri için olmayabilir"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Sistem Kullanıcı Arayüzü Ayarlayıcı, Android kullanıcı arayüzünde değişiklikler yapmanız ve arayüzü özelleştirmeniz için ekstra yollar sağlar. Bu deneysel özellikler değişebilir, bozulabilir veya gelecekteki sürümlerde yer almayabilir. Dikkatli bir şekilde devam edin."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Kutu ekle"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> konumuna taşı"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> konumuna ekle"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Konum geçersiz."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Konum: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kutu eklendi"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kutu kaldırıldı"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index d479eb9..2659f9f 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Зліва на <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Справа на <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Збережено в додатку <xliff:g id="APP">%1$s</xliff:g> у робочому профілі"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Збережено в додатку <xliff:g id="APP">%1$s</xliff:g> в особистому профілі"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Файли"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"Додаток <xliff:g id="APPNAME">%1$s</xliff:g> виявив цей знімок екрана."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> та інші відкриті додатки виявили цей знімок екрана."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Утримуйте, щоб налаштувати віджети"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Налаштувати віджети"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Значок додатка для вимкненого віджета"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Редагувати віджет"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Видалити"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Додати віджет"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Закрити віджети на заблокованому екрані"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Налаштувати віджети"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Віджети на заблокованому екрані"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"виберіть віджет"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"видалити віджет"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"розмістити вибраний віджет"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Змінити користувача"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"спадне меню"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усі додатки й дані з цього сеансу буде видалено."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Відкрити налаштування відтворення"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Повзунки гучності розгорнуто"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Повзунки гучності згорнуто"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"вимкнути звук %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"увімкнути звук %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Де відтворюється <xliff:g id="LABEL">%s</xliff:g>:"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Де гратиме аудіо:"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Триває виклик"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"Рядок стану"</string>
     <string name="demo_mode" msgid="263484519766901593">"Демо-режим інтерфейсу системи"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Погане з’єднання із супутником"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Хороше з’єднання із супутником"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Доступне з’єднання із супутником"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Супутниковий сигнал SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Робочий профіль"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Це цікаво, але будьте обачні"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner пропонує нові способи налаштувати та персоналізувати інтерфейс користувача Android. Ці експериментальні функції можуть змінюватися, не працювати чи зникати в майбутніх версіях. Будьте обачні."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Додати панель"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Перемістити на позицію <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Додати на позицію <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Позиція недійсна."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Позиція <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Опцію додано"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Опцію вилучено"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 3bf6134..2a7fda1 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"بایاں احاطہ <xliff:g id="PERCENT">%1$d</xliff:g> فیصد"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"دایاں احاطہ <xliff:g id="PERCENT">%1$d</xliff:g> فیصد"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"دفتری پروفائل میں <xliff:g id="APP">%1$s</xliff:g> میں محفوظ کی گئی"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"نجی پروفائل میں <xliff:g id="APP">%1$s</xliff:g> میں محفوظ کیا گیا"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"فائلز"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"‫<xliff:g id="APPNAME">%1$s</xliff:g> نے اس اسکرین شاٹ کا پتا لگایا۔"</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"‫<xliff:g id="APPNAME">%1$s</xliff:g> اور دیگر کھلی ایپس نے اس اسکرین شاٹ کا پتا لگایا۔"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ویجٹس کو حسب ضرورت بنانے کے لیے لانگ پریس کریں"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ویجیٹس کو حسب ضرورت بنائیں"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"غیر فعال ویجیٹ کے لئے ایپ آئیکن"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"ویجیٹ میں ترمیم کریں"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ہٹائیں"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ویجیٹ شامل کریں"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"مقفل اسکرین پر ویجٹس بند کریں"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ویجیٹس کو حسب ضرورت بنائیں"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"مقفل اسکرین پر ویجیٹس"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ویجیٹ منتخب کریں"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ویجیٹ ہٹائیں"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"منتخب ویجیٹ رکھیں"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"صارف سوئچ کریں"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"پل ڈاؤن مینیو"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"اس سیشن میں موجود سبھی ایپس اور ڈیٹا کو حذف کر دیا جائے گا۔"</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"آؤٹ پٹ کی ترتیبات درج کریں"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"والیوم سلائیڈرز کو پھیلا دیا گیا"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"والیوم سلائیڈرز سکیڑا گیا"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"‏%s خاموش کریں"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"‏%s غیر خاموش کریں"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> پر چل رہی ہے"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"آڈیو اس پر چلے گی"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"کال کی جا رہی ہے"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"‏سسٹم UI ٹیونر"</string>
     <string name="status_bar" msgid="4357390266055077437">"اسٹیٹس بار"</string>
     <string name="demo_mode" msgid="263484519766901593">"‏سسٹم UI ڈیمو موڈ"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"سیٹلائٹ، کنکشن خراب ہے"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"سیٹلائٹ، کنکشن اچھا ہے"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"سیٹلائٹ، کنکشن دستیاب ہے"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"‏سیٹلائٹ SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"دفتری پروفائل"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"کچھ کیلئے دلچسپ لیکن سبھی کیلئے نہیں"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"‏سسٹم UI ٹیونر Android صارف انٹر فیس میں ردوبدل کرنے اور اسے حسب ضرورت بنانے کیلئے آپ کو اضافی طریقے دیتا ہے۔ یہ تجرباتی خصوصیات مستقبل کی ریلیزز میں تبدیل ہو سکتی، رک سکتی یا غائب ہو سکتی ہیں۔ احتیاط کے ساتھ آگے بڑھیں۔"</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"ٹائل شامل کریں"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> میں منتقل کریں"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"پوزیشن <xliff:g id="POSITION">%1$d</xliff:g> میں شامل کریں"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"پوزیشن غلط ہے۔"</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"پوزیشن <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ٹائل کو شامل کیا گیا"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ٹائل کو ہٹا دیا گیا"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 73bd409..0d7709c 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Chap chegara <xliff:g id="PERCENT">%1$d</xliff:g> foiz"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Oʻng chegara <xliff:g id="PERCENT">%1$d</xliff:g> foiz"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Ish profilidagi <xliff:g id="APP">%1$s</xliff:g> ilovasiga saqlandi"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Yopiq profildagi <xliff:g id="APP">%1$s</xliff:g> ilovasiga saqlandi"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fayllar"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> skrinshot olinganini aniqladi."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> va boshqa ochiq ilovalar skrinshot olinganini aniqladi."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Vidjetlarni sozlash uchun bosib turing"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Vidjetlarni moslashtirish"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Faolsizlantirilgan vidjet uchun ilova belgisi"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Vidjetni tahrirlash"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Olib tashlash"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Vidjet kiritish"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Ekran qulfida vidjetlarni yopish"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Vidjetlarni moslash"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Ekran qulfidagi vidjetlar"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"vidjet tanlash"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"vidjetni olib tashlash"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"tanlangan vidjetni joylash"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Foydalanuvchini almashtirish"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"tortib tushiriladigan menyu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ushbu seansdagi barcha ilovalar va ma’lumotlar o‘chirib tashlanadi."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Chiqarish sozlamalarini kiritish"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Tovush slayderlari yoyilgan"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Tovush slayderlari yigʻilgan"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%sni ovozsiz qilish"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s ovozini chiqarish"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g>da ijro etilmoqda"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio ijro etiladi"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Chaqiruv yoniq"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"SystemUI Tuner"</string>
     <string name="status_bar" msgid="4357390266055077437">"Holat qatori"</string>
     <string name="demo_mode" msgid="263484519766901593">"Tizim interfeysi demo rejimi"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Sputnik, aloqa sifati past"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Sputnik, aloqa sifati yaxshi"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Sputnik, aloqa mavjud"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Sputnik SOS"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Ish profili"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Diqqat!"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner yordamida siz Android foydalanuvchi interfeysini tuzatish va o‘zingizga moslashtirishingiz mumkin. Ushbu tajribaviy funksiyalar o‘zgarishi, buzilishi yoki keyingi versiyalarda olib tashlanishi mumkin. Ehtiyot bo‘lib davom eting."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Katakcha kiritish"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Bu joyga olish: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Bu joyga kiritish: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Pozitsiya yaroqsiz."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Joylashuv: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Katakcha kiritildi"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Katakcha olib tashlandi"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index fb4cd13..2ddcd6d 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Cạnh trái <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Cạnh phải <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Đã lưu vào <xliff:g id="APP">%1$s</xliff:g> trong hồ sơ công việc"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Đã lưu vào <xliff:g id="APP">%1$s</xliff:g> trong hồ sơ riêng tư"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> đã phát hiện thấy ảnh chụp màn hình này."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> và các ứng dụng đang mở khác đã phát hiện thấy ảnh chụp màn hình này."</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Nhấn và giữ để tuỳ chỉnh tiện ích"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Tuỳ chỉnh tiện ích"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Biểu tượng ứng dụng của tiện ích đã bị vô hiệu hoá"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Chỉnh sửa tiện ích"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Xoá"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Thêm tiện ích"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Đóng các tiện ích trên màn hình khoá"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Tuỳ chỉnh tiện ích"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Các tiện ích trên màn hình khoá"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"chọn tiện ích"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"xoá tiện ích"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"đặt tiện ích đã chọn vào vị trí"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Chuyển đổi người dùng"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"trình đơn kéo xuống"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tất cả ứng dụng và dữ liệu trong phiên này sẽ bị xóa."</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Mở phần cài đặt thiết bị đầu ra"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Đã mở rộng thanh trượt âm lượng"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Đã thu gọn thanh trượt âm lượng"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"tắt tiếng %s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"bật tiếng %s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Đang phát <xliff:g id="LABEL">%s</xliff:g> trên"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Âm thanh sẽ phát trên"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Đang gọi điện"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Bộ điều hướng giao diện người dùng hệ thống"</string>
     <string name="status_bar" msgid="4357390266055077437">"Thanh trạng thái"</string>
     <string name="demo_mode" msgid="263484519766901593">"Chế độ thử nghiệm giao diện người dùng hệ thống"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Kết nối vệ tinh kém"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Kết nối vệ tinh tốt"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Hiện có kết nối vệ tinh"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Liên lạc khẩn cấp qua vệ tinh"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"Hồ sơ công việc"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"Thú vị đối với một số người nhưng không phải tất cả"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"Bộ điều hướng giao diện người dùng hệ thống cung cấp thêm cho bạn những cách chỉnh sửa và tùy chỉnh giao diện người dùng Android. Những tính năng thử nghiệm này có thể thay đổi, hỏng hoặc biến mất trong các phiên bản tương lai. Hãy thận trọng khi tiếp tục."</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Thêm ô"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Di chuyển tới <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Thêm vào vị trí <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Vị trí không hợp lệ."</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Vị trí <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Đã thêm thẻ thông tin"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Đã xóa thẻ thông tin"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 47de3ea..64e37ce 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"左侧边界百分之 <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"右侧边界百分之 <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"已保存到工作资料名下的 <xliff:g id="APP">%1$s</xliff:g>中"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"已保存在私密个人资料中的<xliff:g id="APP">%1$s</xliff:g>内"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"文件"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> 检测到此屏幕截图。"</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> 及其他打开的应用检测到此屏幕截图。"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"长按即可自定义微件"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"自定义微件"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"已停用微件的应用图标"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"修改微件"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"移除"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"添加微件"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"关闭锁定屏幕上的微件"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"自定义微件"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"锁定屏幕上的微件"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"选择微件"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"移除微件"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"放置所选微件"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切换用户"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉菜单"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"此会话中的所有应用和数据都将被删除。"</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"进入输出设置"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"音量滑块已展开"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"音量滑块已收起"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"静音“%s”"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"取消静音“%s”"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g>播放位置:"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"音频播放位置:"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"正在通话"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"系统界面调节工具"</string>
     <string name="status_bar" msgid="4357390266055077437">"状态栏"</string>
     <string name="demo_mode" msgid="263484519766901593">"系统界面演示模式"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"卫星,连接质量不佳"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"卫星,连接质量良好"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"卫星,可连接"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"卫星紧急呼救"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"工作资料"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"并不适合所有用户"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"系统界面调节工具可让您以更多方式调整及定制 Android 界面。在日后推出的版本中,这些实验性功能可能会变更、失效或消失。操作时请务必谨慎。"</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"添加功能块"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"移至 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"添加到位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"位置无效。"</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"已添加功能块"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"已移除功能块"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 491ec4d..714e479 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"左方邊界 <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"右方邊界 <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"已儲存在工作設定檔的「<xliff:g id="APP">%1$s</xliff:g>」中"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"已儲存在私人設定檔的「<xliff:g id="APP">%1$s</xliff:g>」中"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"檔案"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> 偵測到此螢幕截圖。"</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> 和其他開啟的應用程式偵測到此螢幕截圖。"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"長按即可自訂小工具"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"自訂小工具"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"已停用小工具的應用程式圖示"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"編輯小工具"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"移除"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"新增小工具"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"關閉上鎖畫面上的小工具"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"自訂小工具"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"上鎖畫面上的小工具"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"揀小工具"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"移除小工具"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"放置所選小工具"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切換使用者"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉式選單"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會被刪除。"</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"輸入輸出設定"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"打開咗音量滑桿"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"閂埋咗音量滑桿"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"將%s設定為靜音"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"取消%s的靜音設定"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"正在播放「<xliff:g id="LABEL">%s</xliff:g>」的裝置:"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"音訊播放媒體"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"通話中"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"系統使用者介面調諧器"</string>
     <string name="status_bar" msgid="4357390266055077437">"狀態列"</string>
     <string name="demo_mode" msgid="263484519766901593">"系統使用者介面示範模式"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"衛星,連線質素唔好"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"衛星,連線質素好"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"衛星,可以連線"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"緊急衛星連接"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"工作設定檔"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"這只是測試版本,並不包含完整功能"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"使用者介面調諧器讓你以更多方法修改和自訂 Android 使用者介面。但請小心,這些實驗功能可能會在日後發佈時更改、分拆或消失。"</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"加圖塊"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"移去 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"加去位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"位置冇效。"</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"加咗圖塊"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"移除咗圖塊"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index f60b392..495a055 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -97,8 +97,7 @@
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"左側邊界百分之 <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"右側邊界百分之 <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_work_profile_notification" msgid="203041724052970693">"已儲存在工作資料夾的「<xliff:g id="APP">%1$s</xliff:g>」中"</string>
-    <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) -->
-    <skip />
+    <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"已儲存在個人資料夾的「<xliff:g id="APP">%1$s</xliff:g>」"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"檔案"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"「<xliff:g id="APPNAME">%1$s</xliff:g>」偵測到這張螢幕截圖。"</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"「<xliff:g id="APPNAME">%1$s</xliff:g>」和其他開啟的應用程式偵測到這張螢幕截圖。"</string>
@@ -448,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"長按即可自訂小工具"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"自訂小工具"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"所停用小工具的應用程式圖示"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"編輯小工具"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"移除"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"新增小工具"</string>
@@ -461,8 +462,9 @@
     <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"關閉螢幕鎖定畫面上的小工具"</string>
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"自訂小工具"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"螢幕鎖定畫面上的小工具"</string>
-    <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) -->
-    <skip />
+    <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"選取小工具"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"移除小工具"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"放置所選小工具"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切換使用者"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉式選單"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會刪除。"</string>
@@ -627,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"進入輸出設定"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"音量滑桿已展開"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"音量滑桿已收合"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"將%s設為靜音"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"將%s取消靜音"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"正在播放「<xliff:g id="LABEL">%s</xliff:g>」的裝置:"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"音訊播放位置"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"通話中"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"系統使用者介面調整精靈"</string>
     <string name="status_bar" msgid="4357390266055077437">"狀態列"</string>
     <string name="demo_mode" msgid="263484519766901593">"系統 UI 展示模式"</string>
@@ -660,8 +667,7 @@
     <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"衛星,連線品質不佳"</string>
     <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"衛星,連線品質良好"</string>
     <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"衛星,可連線"</string>
-    <!-- no translation found for satellite_connected_carrier_text (118524195198532589) -->
-    <skip />
+    <string name="satellite_connected_carrier_text" msgid="118524195198532589">"緊急衛星連線"</string>
     <string name="accessibility_managed_profile" msgid="4703836746209377356">"工作資料夾"</string>
     <string name="tuner_warning_title" msgid="7721976098452135267">"有趣與否,見仁見智"</string>
     <string name="tuner_warning" msgid="1861736288458481650">"系統使用者介面調整精靈可讓你透過其他方式,調整及自訂 Android 使用者介面。這些實驗性功能隨著版本更新可能會變更、損壞或消失,執行時請務必謹慎。"</string>
@@ -866,8 +872,7 @@
     <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"新增圖塊"</string>
     <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"移至 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"新增到位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
-    <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) -->
-    <skip />
+    <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"位置無效。"</string>
     <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"已新增設定方塊"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"已移除設定方塊"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index d40f2c1..f5ee5a0 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -447,6 +447,8 @@
     <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Cindezela isikhathi eside ukuze wenze ngokwezifiso amawijethi"</string>
     <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Yenza ngokwezifiso amawijethi"</string>
     <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Isithonjana se-app sewijethi evaliwe"</string>
+    <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) -->
+    <skip />
     <string name="edit_widget" msgid="9030848101135393954">"Hlela amawijethi"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Susa"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Engeza iwijethi"</string>
@@ -461,6 +463,8 @@
     <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Yenza ngokwezifiso amawijethi"</string>
     <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Amawijethi ekukhiyeni isikrini"</string>
     <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"khetha iwijethi"</string>
+    <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"susa iwijethi"</string>
+    <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"beka iwijethi ekhethiwe"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Shintsha umsebenzisi"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"imenyu yokudonsela phansi"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Wonke ama-app nedatha kulesi sikhathi azosuswa."</string>
@@ -625,12 +629,17 @@
     <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Faka amasethingi wokuphumayo"</string>
     <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Izilayidi zevolumu zinwetshiwe"</string>
     <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Izilayidi zevolumu zigoqiwe"</string>
-    <string name="volume_panel_hint_mute" msgid="6962563028495243738">"thulisa i-%s"</string>
-    <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"susa ukuthula kwe-%s"</string>
+    <!-- no translation found for volume_panel_hint_mute (2153922288568199077) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_muted (1124844870181285320) -->
+    <skip />
+    <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) -->
+    <skip />
     <string name="media_output_label_title" msgid="872824698593182505">"Idlala ku-<xliff:g id="LABEL">%s</xliff:g>"</string>
     <string name="media_output_title_without_playing" msgid="3825663683169305013">"Umsindo uzodlala"</string>
-    <!-- no translation found for media_output_title_ongoing_call (208426888064112006) -->
-    <skip />
+    <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Ifonela kokuthi"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"Isishuni se-UI yesistimu"</string>
     <string name="status_bar" msgid="4357390266055077437">"Ibha yesimo"</string>
     <string name="demo_mode" msgid="263484519766901593">"Imodi yedemo ye-UI yesistimu"</string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a7a6d5b..b960813 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -285,6 +285,9 @@
     the amount by the view is positioned above the screen before the animation starts. -->
     <dimen name="heads_up_appear_y_above_screen">32dp</dimen>
 
+    <!-- padding between the old and new heads up notifications for the hun cycling animation -->
+    <dimen name="heads_up_cycling_padding">8dp</dimen>
+
     <!-- padding between the heads up and the statusbar -->
     <dimen name="heads_up_status_bar_padding">8dp</dimen>
 
@@ -430,6 +433,7 @@
     <dimen name="overlay_button_corner_radius">16dp</dimen>
     <!-- Margin between successive chips -->
     <dimen name="overlay_action_chip_margin_start">8dp</dimen>
+    <dimen name="shelf_action_chip_margin_start">12dp</dimen>
     <dimen name="overlay_action_chip_padding_vertical">12dp</dimen>
     <dimen name="overlay_action_chip_icon_size">24sp</dimen>
     <!-- Padding on each side of the icon for icon-only chips -->
@@ -445,6 +449,8 @@
     <dimen name="overlay_preview_container_margin">8dp</dimen>
     <dimen name="overlay_action_container_margin_horizontal">8dp</dimen>
     <dimen name="overlay_action_container_margin_bottom">6dp</dimen>
+    <!-- minimum distance to the left, right or bottom edges. -->
+    <dimen name="overlay_action_container_minimum_edge_spacing">12dp</dimen>
     <dimen name="overlay_bg_protection_height">242dp</dimen>
     <dimen name="overlay_action_container_corner_radius">20dp</dimen>
     <dimen name="overlay_action_container_padding_vertical">8dp</dimen>
@@ -907,10 +913,6 @@
         obvious when corner radii differ.-->
     <dimen name="communal_enforced_rounded_corner_max_radius">28dp</dimen>
 
-    <!-- Width and height used to filter widgets displayed in the communal widget picker -->
-    <dimen name="communal_widget_picker_desired_width">424dp</dimen>
-    <dimen name="communal_widget_picker_desired_height">282dp</dimen>
-
     <!-- The width/height of the unlock icon view on keyguard. -->
     <dimen name="keyguard_lock_height">42dp</dimen>
     <dimen name="keyguard_lock_padding">20dp</dimen>
@@ -1707,12 +1709,12 @@
     <dimen name="wallet_button_horizontal_padding">24dp</dimen>
     <dimen name="wallet_button_vertical_padding">8dp</dimen>
 
-    <!-- Ongoing call chip -->
-    <dimen name="ongoing_call_chip_side_padding">12dp</dimen>
-    <dimen name="ongoing_call_chip_icon_size">16dp</dimen>
+    <!-- Ongoing activity chip -->
+    <dimen name="ongoing_activity_chip_side_padding">12dp</dimen>
+    <dimen name="ongoing_activity_chip_icon_size">16dp</dimen>
     <!-- The padding between the icon and the text. -->
-    <dimen name="ongoing_call_chip_icon_text_padding">4dp</dimen>
-    <dimen name="ongoing_call_chip_corner_radius">28dp</dimen>
+    <dimen name="ongoing_activity_chip_icon_text_padding">4dp</dimen>
+    <dimen name="ongoing_activity_chip_corner_radius">28dp</dimen>
 
     <!-- Status bar user chip -->
     <dimen name="status_bar_user_chip_avatar_size">16dp</dimen>
@@ -1957,15 +1959,6 @@
     <dimen name="broadcast_dialog_btn_minHeight">44dp</dimen>
     <dimen name="broadcast_dialog_margin">16dp</dimen>
 
-    <!-- Contrast dialog -->
-    <dimen name="contrast_dialog_button_total_size">90dp</dimen>
-    <dimen name="contrast_dialog_button_inner_size">82dp</dimen>
-    <dimen name="contrast_dialog_button_radius">20dp</dimen>
-    <dimen name="contrast_dialog_button_stroke_width">4dp</dimen>
-    <dimen name="contrast_dialog_button_text_size">14sp</dimen>
-    <dimen name="contrast_dialog_button_text_spacing">4dp</dimen>
-    <dimen name="contrast_dialog_button_horizontal_spacing">16dp</dimen>
-
     <!-- Shadow for dream overlay clock complication -->
     <dimen name="dream_overlay_clock_key_text_shadow_dx">0dp</dimen>
     <dimen name="dream_overlay_clock_key_text_shadow_dy">0dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 45bcd82..6df48a0 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -384,6 +384,8 @@
 
     <!-- Content description for the app logo icon on biometric prompt. [CHAR LIMIT=NONE] -->
     <string name="biometric_dialog_logo">App logo</string>
+    <!-- List of packages for which we want to show overridden logo. For example, an app overrides its launcher logo, if it's in this array, biometric dialog shows the overridden logo; otherwise biometric dialog still shows the default application info icon. [CHAR LIMIT=NONE] -->
+    <string-array name="biometric_dialog_package_names_for_logo_with_overrides" />
     <!-- Message shown when a biometric is authenticated, asking the user to confirm authentication [CHAR LIMIT=30] -->
     <string name="biometric_dialog_confirm">Confirm</string>
     <!-- Button name on BiometricPrompt shown when a biometric is detected but not authenticated. Tapping the button resumes authentication [CHAR LIMIT=30] -->
@@ -763,7 +765,7 @@
     <!-- QuickSettings: Wifi secondary label shown when the wifi is being enabled [CHAR LIMIT=NONE] -->
     <string name="quick_settings_wifi_secondary_label_transient">Turning on&#8230;</string>
     <!-- QuickSettings: Cast title [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_cast_title">Screen Cast</string>
+    <string name="quick_settings_cast_title">Cast</string>
     <!-- QuickSettings: Cast detail panel, status text when casting [CHAR LIMIT=NONE] -->
     <string name="quick_settings_casting">Casting</string>
     <!-- QuickSettings: Cast detail panel, default device name [CHAR LIMIT=NONE] -->
@@ -900,15 +902,6 @@
     <!-- QuickSettings: Label for the toggle that controls whether One-handed mode is enabled. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_onehanded_label">One-handed mode</string>
 
-    <!-- QuickSettings: Contrast tile [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_contrast_label">Contrast</string>
-    <!-- QuickSettings: Contrast tile description: standard [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_contrast_standard">Standard</string>
-    <!-- QuickSettings: Contrast tile description: medium [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_contrast_medium">Medium</string>
-    <!-- QuickSettings: Contrast tile description: high [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_contrast_high">High</string>
-
     <!-- Hearing devices -->
     <!-- QuickSettings: Hearing devices [CHAR LIMIT=NONE] -->
     <string name="quick_settings_hearing_devices_label">Hearing devices</string>
@@ -1158,6 +1151,8 @@
     <string name="button_to_configure_widgets_text">Customize widgets</string>
     <!-- Description for the App icon of disabled widget. [CHAR LIMIT=NONE] -->
     <string name="icon_description_for_disabled_widget">App icon for disabled widget</string>
+    <!-- Description for the App icon of a package that is currently being installed. [CHAR LIMIT=NONE] -->
+    <string name="icon_description_for_pending_widget">App icon for a widget being installed</string>
     <!-- Label for the button which configures widgets [CHAR LIMIT=NONE] -->
     <string name="edit_widget">Edit widget</string>
     <!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] -->
@@ -1634,9 +1629,15 @@
     <string name="volume_panel_collapsed_sliders">Volume sliders collapsed</string>
 
     <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to mute media [CHAR_LIMIT=NONE] -->
-    <string name="volume_panel_hint_mute">mute %s</string>
+    <string name="volume_panel_hint_mute">Mute %s</string>
     <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to unmute media [CHAR_LIMIT=NONE] -->
-    <string name="volume_panel_hint_unmute">unmute %s</string>
+    <string name="volume_panel_hint_unmute">Unmute %s</string>
+
+    <!-- Hint for accessibility. This is announced when the stream is muted [CHAR_LIMIT=NONE] -->
+    <string name="volume_panel_hint_muted">muted</string>
+
+    <!-- Hint for accessibility. This is announced when ring mode is set to Vibrate. [CHAR_LIMIT=NONE] -->
+    <string name="volume_panel_hint_vibrate">vibrate</string>
 
     <!-- Title with application label for media output settings when there is media playing. [CHAR LIMIT=20] -->
     <string name="media_output_label_title">Playing <xliff:g id="label" example="Music Player">%s</xliff:g> on</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 69de45e..393a1aa 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -175,21 +175,21 @@
     </style>
 
     <style name="TextAppearance.AuthCredential.OldTitle">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:paddingTop">12dp</item>
         <item name="android:paddingHorizontal">24dp</item>
         <item name="android:textSize">24sp</item>
     </style>
 
     <style name="TextAppearance.AuthCredential.OldSubtitle">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:paddingTop">8dp</item>
         <item name="android:paddingHorizontal">24dp</item>
         <item name="android:textSize">16sp</item>
     </style>
 
     <style name="TextAppearance.AuthCredential.OldDescription">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:paddingTop">8dp</item>
         <item name="android:paddingHorizontal">24dp</item>
         <item name="android:textSize">14sp</item>
@@ -205,7 +205,7 @@
     </style>
 
     <style name="TextAppearance.AuthCredential.Title" parent="TextAppearance.Material3.HeadlineSmall" >
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
     </style>
 
@@ -257,7 +257,7 @@
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Title">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">24dp</item>
         <item name="android:textSize">36dp</item>
         <item name="android:focusable">true</item>
@@ -265,14 +265,14 @@
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Subtitle">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">20dp</item>
         <item name="android:textSize">18sp</item>
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
     </style>
 
     <style name="TextAppearance.AuthNonBioCredential.Description">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:layout_marginTop">20dp</item>
         <item name="android:textSize">18sp</item>
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
@@ -523,10 +523,6 @@
         <item name="android:windowExitAnimation">@anim/instant_fade_out</item>
     </style>
 
-    <style name="Theme.SystemUI.ContrastDialog" parent="@android:style/Theme.DeviceDefault.Dialog">
-        <item name="android:windowBackground">@android:color/transparent</item>
-    </style>
-
     <style name="Theme.SystemUI.QuickSettings.Dialog" parent="@style/Theme.SystemUI.Dialog.QuickSettings">
     </style>
 
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
index 422f02f..8979ef1 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
@@ -16,7 +16,6 @@
 package com.android.systemui.biometrics
 
 import android.Manifest
-import android.annotation.IntDef
 import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
 import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
 import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX
@@ -39,14 +38,9 @@
 import android.view.accessibility.AccessibilityEvent
 import android.view.accessibility.AccessibilityManager
 import com.android.internal.widget.LockPatternUtils
-import java.lang.annotation.Retention
-import java.lang.annotation.RetentionPolicy
+import com.android.systemui.biometrics.shared.model.PromptKind
 
 object Utils {
-    const val CREDENTIAL_PIN = 1
-    const val CREDENTIAL_PATTERN = 2
-    const val CREDENTIAL_PASSWORD = 3
-
     /** Base set of layout flags for fingerprint overlay widgets. */
     const val FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS =
         (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
@@ -91,17 +85,16 @@
         (promptInfo.authenticators and Authenticators.BIOMETRIC_WEAK) != 0
 
     @JvmStatic
-    @CredentialType
-    fun getCredentialType(utils: LockPatternUtils, userId: Int): Int =
+    fun getCredentialType(utils: LockPatternUtils, userId: Int): PromptKind =
         when (utils.getKeyguardStoredPasswordQuality(userId)) {
-            PASSWORD_QUALITY_SOMETHING -> CREDENTIAL_PATTERN
+            PASSWORD_QUALITY_SOMETHING -> PromptKind.Pattern
             PASSWORD_QUALITY_NUMERIC,
-            PASSWORD_QUALITY_NUMERIC_COMPLEX -> CREDENTIAL_PIN
+            PASSWORD_QUALITY_NUMERIC_COMPLEX -> PromptKind.Pin
             PASSWORD_QUALITY_ALPHABETIC,
             PASSWORD_QUALITY_ALPHANUMERIC,
             PASSWORD_QUALITY_COMPLEX,
-            PASSWORD_QUALITY_MANAGED -> CREDENTIAL_PASSWORD
-            else -> CREDENTIAL_PASSWORD
+            PASSWORD_QUALITY_MANAGED -> PromptKind.Password
+            else -> PromptKind.Password
         }
 
     @JvmStatic
@@ -129,8 +122,4 @@
         return windowMetrics?.windowInsets?.getInsets(WindowInsets.Type.navigationBars())
             ?: Insets.NONE
     }
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD)
-    annotation class CredentialType
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
similarity index 64%
rename from packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
rename to packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
index a97e2dc..b99c514 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 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.
@@ -16,16 +16,21 @@
 
 package com.android.systemui.biometrics.shared.model
 
-import com.android.systemui.biometrics.Utils
-
-// TODO(b/251476085): this should eventually replace Utils.CredentialType
-/** Credential options for biometric prompt. Shadows [Utils.CredentialType]. */
 sealed interface PromptKind {
+    object None : PromptKind
+
     data class Biometric(
+        /** The available modalities for the authentication on the prompt. */
         val activeModalities: BiometricModalities = BiometricModalities(),
+        // TODO(b/330908557): Use this value to decide whether to show two pane layout, instead of
+        // simply depending on rotations.
+        val showTwoPane: Boolean = false,
     ) : PromptKind
 
     object Pin : PromptKind
     object Pattern : PromptKind
     object Password : PromptKind
+
+    fun isBiometric() = this is Biometric
+    fun isCredential() = (this is Pin) or (this is Pattern) or (this is Password)
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
index 8ac1de8..c33b7ce 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
@@ -99,7 +99,7 @@
             final float startScale = sourceRectHint.width() <= sourceRectHint.height()
                     ? (float) destinationBounds.width() / sourceBounds.width()
                     : (float) destinationBounds.height() / sourceBounds.height();
-            scale = (1 - progress) * startScale + progress * endScale;
+            scale = Math.min((1 - progress) * startScale + progress * endScale, 1.0f);
         }
         final float left = destinationBounds.left - (insets.left + sourceBounds.left) * scale;
         final float top = destinationBounds.top - (insets.top + sourceBounds.top) * scale;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index d191a3c..d5bc10a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -65,7 +65,7 @@
     /**
      * Sent when some system ui state changes.
      */
-    void onSystemUiStateChanged(int stateFlags) = 16;
+    void onSystemUiStateChanged(long stateFlags) = 16;
 
     /**
      * Sent when suggested rotation button could be shown
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 94b6fd4..090033d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -162,5 +162,10 @@
     oneway void setOverrideHomeButtonLongPress(long duration, float slopMultiplier, boolean haptic)
             = 55;
 
-    // Next id = 56
+    /**
+     * Notifies to toggle quick settings panel.
+     */
+    oneway void toggleQuickSettingsPanel() = 56;
+
+    // Next id = 57
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java
deleted file mode 100644
index 0b0df83..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2016 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.shared.recents.model;
-
-import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
-import static android.graphics.Bitmap.Config.ARGB_8888;
-
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
-import android.util.Log;
-import android.view.WindowInsetsController.Appearance;
-import android.window.TaskSnapshot;
-
-import java.util.HashMap;
-
-/**
- * Data for a single thumbnail.
- */
-public class ThumbnailData {
-
-    public final Bitmap thumbnail;
-    public int orientation;
-    public int rotation;
-    public Rect insets;
-    public Rect letterboxInsets;
-    public boolean reducedResolution;
-    public boolean isRealSnapshot;
-    public boolean isTranslucent;
-    public int windowingMode;
-    public @Appearance int appearance;
-    public float scale;
-    public long snapshotId;
-
-    public ThumbnailData() {
-        thumbnail = null;
-        orientation = ORIENTATION_UNDEFINED;
-        rotation = ROTATION_UNDEFINED;
-        insets = new Rect();
-        letterboxInsets = new Rect();
-        reducedResolution = false;
-        scale = 1f;
-        isRealSnapshot = true;
-        isTranslucent = false;
-        windowingMode = WINDOWING_MODE_UNDEFINED;
-        snapshotId = 0;
-    }
-
-    public void recycleBitmap() {
-        if (thumbnail != null) {
-            thumbnail.recycle();
-        }
-    }
-
-    private static Bitmap makeThumbnail(TaskSnapshot snapshot) {
-        Bitmap thumbnail = null;
-        try (final HardwareBuffer buffer = snapshot.getHardwareBuffer()) {
-            if (buffer != null) {
-                thumbnail = Bitmap.wrapHardwareBuffer(buffer, snapshot.getColorSpace());
-            }
-        } catch (IllegalArgumentException ex) {
-            // TODO(b/157562905): Workaround for a crash when we get a snapshot without this state
-            Log.e("ThumbnailData", "Unexpected snapshot without USAGE_GPU_SAMPLED_IMAGE: "
-                    + snapshot.getHardwareBuffer(), ex);
-        }
-        if (thumbnail == null) {
-            Point taskSize = snapshot.getTaskSize();
-            thumbnail = Bitmap.createBitmap(taskSize.x, taskSize.y, ARGB_8888);
-            thumbnail.eraseColor(Color.BLACK);
-        }
-        return thumbnail;
-    }
-
-    public static HashMap<Integer, ThumbnailData> wrap(int[] taskIds, TaskSnapshot[] snapshots) {
-        HashMap<Integer, ThumbnailData> temp = new HashMap<>();
-        if (taskIds == null || snapshots == null || taskIds.length != snapshots.length) {
-            return temp;
-        }
-
-        for (int i = snapshots.length - 1; i >= 0; i--) {
-            temp.put(taskIds[i], new ThumbnailData(snapshots[i]));
-        }
-        return temp;
-    }
-
-    public ThumbnailData(TaskSnapshot snapshot) {
-        thumbnail = makeThumbnail(snapshot);
-        insets = new Rect(snapshot.getContentInsets());
-        letterboxInsets = new Rect(snapshot.getLetterboxInsets());
-        orientation = snapshot.getOrientation();
-        rotation = snapshot.getRotation();
-        reducedResolution = snapshot.isLowResolution();
-        // TODO(b/149579527): Pass task size instead of computing scale.
-        // Assume width and height were scaled the same; compute scale only for width
-        scale = (float) thumbnail.getWidth() / snapshot.getTaskSize().x;
-        isRealSnapshot = snapshot.isRealSnapshot();
-        isTranslucent = snapshot.isTranslucent();
-        windowingMode = snapshot.getWindowingMode();
-        appearance = snapshot.getAppearance();
-        snapshotId = snapshot.getId();
-    }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.kt
new file mode 100644
index 0000000..dcf7754
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 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.shared.recents.model
+
+import android.app.WindowConfiguration
+import android.content.res.Configuration
+import android.graphics.Bitmap
+import android.graphics.Bitmap.Config.ARGB_8888
+import android.graphics.Color
+import android.graphics.Rect
+import android.util.Log
+import android.view.WindowInsetsController.Appearance
+import android.window.TaskSnapshot
+
+/** Data for a single thumbnail. */
+data class ThumbnailData(
+    val thumbnail: Bitmap? = null,
+    var orientation: Int = Configuration.ORIENTATION_UNDEFINED,
+    @JvmField var rotation: Int = WindowConfiguration.ROTATION_UNDEFINED,
+    @JvmField var insets: Rect = Rect(),
+    @JvmField var letterboxInsets: Rect = Rect(),
+    @JvmField var reducedResolution: Boolean = false,
+    @JvmField var isRealSnapshot: Boolean = true,
+    var isTranslucent: Boolean = false,
+    @JvmField var windowingMode: Int = WindowConfiguration.WINDOWING_MODE_UNDEFINED,
+    @JvmField @Appearance var appearance: Int = 0,
+    @JvmField var scale: Float = 1f,
+    var snapshotId: Long = 0,
+) {
+    fun recycleBitmap() {
+        thumbnail?.recycle()
+    }
+
+    companion object {
+        private fun makeThumbnail(snapshot: TaskSnapshot): Bitmap {
+            var thumbnail: Bitmap? = null
+            try {
+                snapshot.hardwareBuffer?.use { buffer ->
+                    thumbnail = Bitmap.wrapHardwareBuffer(buffer, snapshot.colorSpace)
+                }
+            } catch (ex: IllegalArgumentException) {
+                // TODO(b/157562905): Workaround for a crash when we get a snapshot without this
+                // state
+                Log.e(
+                    "ThumbnailData",
+                    "Unexpected snapshot without USAGE_GPU_SAMPLED_IMAGE: " +
+                        "${snapshot.hardwareBuffer}",
+                    ex
+                )
+            }
+
+            return thumbnail
+                ?: Bitmap.createBitmap(snapshot.taskSize.x, snapshot.taskSize.y, ARGB_8888).apply {
+                    eraseColor(Color.BLACK)
+                }
+        }
+
+        @JvmStatic
+        fun wrap(taskIds: IntArray?, snapshots: Array<TaskSnapshot>?): HashMap<Int, ThumbnailData> {
+            return if (taskIds == null || snapshots == null || taskIds.size != snapshots.size) {
+                HashMap()
+            } else {
+                HashMap(taskIds.associateWith { taskId -> fromSnapshot(snapshots[taskId]) })
+            }
+        }
+
+        @JvmStatic
+        fun fromSnapshot(snapshot: TaskSnapshot): ThumbnailData {
+            val thumbnail = makeThumbnail(snapshot)
+            return ThumbnailData(
+                thumbnail = thumbnail,
+                insets = Rect(snapshot.contentInsets),
+                letterboxInsets = Rect(snapshot.letterboxInsets),
+                orientation = snapshot.orientation,
+                rotation = snapshot.rotation,
+                reducedResolution = snapshot.isLowResolution,
+                // TODO(b/149579527): Pass task size instead of computing scale.
+                // Assume width and height were scaled the same; compute scale only for width
+                scale = thumbnail.width.toFloat() / snapshot.taskSize.x,
+                isRealSnapshot = snapshot.isRealSnapshot,
+                isTranslucent = snapshot.isTranslucent,
+                windowingMode = snapshot.windowingMode,
+                appearance = snapshot.appearance,
+                snapshotId = snapshot.id,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index ca63483..845ca5e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -147,7 +147,7 @@
             Log.w(TAG, "Failed to retrieve task snapshot", e);
         }
         if (snapshot != null) {
-            return new ThumbnailData(snapshot);
+            return ThumbnailData.fromSnapshot(snapshot);
         } else {
             return new ThumbnailData();
         }
@@ -167,7 +167,7 @@
             Log.w(TAG, "Failed to take task snapshot", e);
         }
         if (snapshot != null) {
-            return new ThumbnailData(snapshot);
+            return ThumbnailData.fromSnapshot(snapshot);
         } else {
             return new ThumbnailData();
         }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index c08b083..b4377ea 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -22,7 +22,7 @@
 
 import static com.android.systemui.shared.Flags.shadeAllowBackGesture;
 
-import android.annotation.IntDef;
+import android.annotation.LongDef;
 import android.content.Context;
 import android.content.res.Resources;
 import android.view.ViewConfiguration;
@@ -51,92 +51,94 @@
 
     // Overview is disabled, either because the device is in lock task mode, or because the device
     // policy has disabled the feature
-    public static final int SYSUI_STATE_SCREEN_PINNING = 1 << 0;
+    public static final long SYSUI_STATE_SCREEN_PINNING = 1L << 0;
     // The navigation bar is hidden due to immersive mode
-    public static final int SYSUI_STATE_NAV_BAR_HIDDEN = 1 << 1;
+    public static final long SYSUI_STATE_NAV_BAR_HIDDEN = 1L << 1;
     // The notification panel is expanded and interactive (either locked or unlocked), and the
     // quick settings is not expanded
-    public static final int SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED = 1 << 2;
+    public static final long SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED = 1L << 2;
     // The keyguard bouncer is showing
-    public static final int SYSUI_STATE_BOUNCER_SHOWING = 1 << 3;
+    public static final long SYSUI_STATE_BOUNCER_SHOWING = 1L << 3;
     // The navigation bar a11y button should be shown
-    public static final int SYSUI_STATE_A11Y_BUTTON_CLICKABLE = 1 << 4;
+    public static final long SYSUI_STATE_A11Y_BUTTON_CLICKABLE = 1L << 4;
     // The navigation bar a11y button shortcut is available
-    public static final int SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE = 1 << 5;
+    public static final long SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE = 1L << 5;
     // The keyguard is showing and not occluded
-    public static final int SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING = 1 << 6;
+    public static final long SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING = 1L << 6;
     // The recents feature is disabled (either by SUW/SysUI/device policy)
-    public static final int SYSUI_STATE_OVERVIEW_DISABLED = 1 << 7;
+    public static final long SYSUI_STATE_OVERVIEW_DISABLED = 1L << 7;
     // The home feature is disabled (either by SUW/SysUI/device policy)
-    public static final int SYSUI_STATE_HOME_DISABLED = 1 << 8;
+    public static final long SYSUI_STATE_HOME_DISABLED = 1L << 8;
     // The keyguard is showing, but occluded
-    public static final int SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED = 1 << 9;
+    public static final long SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED = 1L << 9;
     // The search feature is disabled (either by SUW/SysUI/device policy)
-    public static final int SYSUI_STATE_SEARCH_DISABLED = 1 << 10;
+    public static final long SYSUI_STATE_SEARCH_DISABLED = 1L << 10;
     // The notification panel is expanded and interactive (either locked or unlocked), and quick
     // settings is expanded.
-    public static final int SYSUI_STATE_QUICK_SETTINGS_EXPANDED = 1 << 11;
+    public static final long SYSUI_STATE_QUICK_SETTINGS_EXPANDED = 1L << 11;
     // Winscope tracing is enabled
-    public static final int SYSUI_STATE_TRACING_ENABLED = 1 << 12;
+    public static final long SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION = 1L << 12;
     // The Assistant gesture should be constrained. It is up to the launcher implementation to
     // decide how to constrain it
-    public static final int SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED = 1 << 13;
+    public static final long SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED = 1L << 13;
     // The bubble stack is expanded. This means that the home gesture should be ignored, since a
     // swipe up is an attempt to close the bubble stack, but that the back gesture should remain
     // enabled (since it's used to navigate back within the bubbled app, or to collapse the bubble
     // stack.
-    public static final int SYSUI_STATE_BUBBLES_EXPANDED = 1 << 14;
+    public static final long SYSUI_STATE_BUBBLES_EXPANDED = 1L << 14;
     // A SysUI dialog is showing.
-    public static final int SYSUI_STATE_DIALOG_SHOWING = 1 << 15;
+    public static final long SYSUI_STATE_DIALOG_SHOWING = 1L << 15;
     // The one-handed mode is active
-    public static final int SYSUI_STATE_ONE_HANDED_ACTIVE = 1 << 16;
+    public static final long SYSUI_STATE_ONE_HANDED_ACTIVE = 1L << 16;
     // Allow system gesture no matter the system bar(s) is visible or not
-    public static final int SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY = 1 << 17;
+    public static final long SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY = 1L << 17;
     // The IME is showing
-    public static final int SYSUI_STATE_IME_SHOWING = 1 << 18;
+    public static final long SYSUI_STATE_IME_SHOWING = 1L << 18;
     // The window magnification is overlapped with system gesture insets at the bottom.
-    public static final int SYSUI_STATE_MAGNIFICATION_OVERLAP = 1 << 19;
+    public static final long SYSUI_STATE_MAGNIFICATION_OVERLAP = 1L << 19;
     // ImeSwitcher is showing
-    public static final int SYSUI_STATE_IME_SWITCHER_SHOWING = 1 << 20;
+    public static final long SYSUI_STATE_IME_SWITCHER_SHOWING = 1L << 20;
     // Device dozing/AOD state
-    public static final int SYSUI_STATE_DEVICE_DOZING = 1 << 21;
+    public static final long SYSUI_STATE_DEVICE_DOZING = 1L << 21;
     // The home feature is disabled (either by SUW/SysUI/device policy)
-    public static final int SYSUI_STATE_BACK_DISABLED = 1 << 22;
+    public static final long SYSUI_STATE_BACK_DISABLED = 1L << 22;
     // The bubble stack is expanded AND the mange menu for bubbles is expanded on top of it.
-    public static final int SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED = 1 << 23;
+    public static final long SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED = 1L << 23;
     // The voice interaction session window is showing
-    public static final int SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 25;
+    public static final long SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING = 1L << 25;
     // Freeform windows are showing in desktop mode
-    public static final int SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1 << 26;
+    public static final long SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1L << 26;
     // Device dreaming state
-    public static final int SYSUI_STATE_DEVICE_DREAMING = 1 << 27;
+    public static final long SYSUI_STATE_DEVICE_DREAMING = 1L << 27;
     // Whether the device is currently awake (as opposed to asleep, see WakefulnessLifecycle).
     // Note that the device is awake on while waking up on, but not while going to sleep.
-    public static final int SYSUI_STATE_AWAKE = 1 << 28;
+    public static final long SYSUI_STATE_AWAKE = 1L << 28;
     // Whether the device is currently transitioning between awake/asleep indicated by
     // SYSUI_STATE_AWAKE.
-    public static final int SYSUI_STATE_WAKEFULNESS_TRANSITION = 1 << 29;
+    public static final long SYSUI_STATE_WAKEFULNESS_TRANSITION = 1L << 29;
     // The notification panel expansion fraction is > 0
-    public static final int SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE = 1 << 30;
+    public static final long SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE = 1L << 30;
     // When keyguard will be dismissed but didn't start animation yet
-    public static final int SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY = 1 << 31;
+    public static final long SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY = 1L << 31;
+    // Physical keyboard shortcuts helper is showing
+    public static final long SYSUI_STATE_SHORTCUT_HELPER_SHOWING = 1L << 32;
 
     // Mask for SystemUiStateFlags to isolate SYSUI_STATE_AWAKE and
     // SYSUI_STATE_WAKEFULNESS_TRANSITION, to match WAKEFULNESS_* constants
-    public static final int SYSUI_STATE_WAKEFULNESS_MASK =
+    public static final long SYSUI_STATE_WAKEFULNESS_MASK =
             SYSUI_STATE_AWAKE | SYSUI_STATE_WAKEFULNESS_TRANSITION;
     // Mirroring the WakefulnessLifecycle#Wakefulness states
-    public static final int WAKEFULNESS_ASLEEP = 0;
-    public static final int WAKEFULNESS_AWAKE = SYSUI_STATE_AWAKE;
-    public static final int WAKEFULNESS_GOING_TO_SLEEP = SYSUI_STATE_WAKEFULNESS_TRANSITION;
-    public static final int WAKEFULNESS_WAKING =
+    public static final long WAKEFULNESS_ASLEEP = 0;
+    public static final long WAKEFULNESS_AWAKE = SYSUI_STATE_AWAKE;
+    public static final long WAKEFULNESS_GOING_TO_SLEEP = SYSUI_STATE_WAKEFULNESS_TRANSITION;
+    public static final long WAKEFULNESS_WAKING =
             SYSUI_STATE_WAKEFULNESS_TRANSITION | SYSUI_STATE_AWAKE;
 
     // Whether the back gesture is allowed (or ignored) by the Shade
     public static final boolean ALLOW_BACK_GESTURE_IN_SHADE = shadeAllowBackGesture();
 
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({SYSUI_STATE_SCREEN_PINNING,
+    @LongDef({SYSUI_STATE_SCREEN_PINNING,
             SYSUI_STATE_NAV_BAR_HIDDEN,
             SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
             SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
@@ -148,7 +150,7 @@
             SYSUI_STATE_OVERVIEW_DISABLED,
             SYSUI_STATE_HOME_DISABLED,
             SYSUI_STATE_SEARCH_DISABLED,
-            SYSUI_STATE_TRACING_ENABLED,
+            SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION,
             SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED,
             SYSUI_STATE_BUBBLES_EXPANDED,
             SYSUI_STATE_DIALOG_SHOWING,
@@ -167,10 +169,11 @@
             SYSUI_STATE_WAKEFULNESS_TRANSITION,
             SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
             SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY,
+            SYSUI_STATE_SHORTCUT_HELPER_SHOWING,
     })
     public @interface SystemUiStateFlags {}
 
-    public static String getSystemUiStateString(int flags) {
+    public static String getSystemUiStateString(long flags) {
         StringJoiner str = new StringJoiner("|");
         if ((flags & SYSUI_STATE_SCREEN_PINNING) != 0) {
             str.add("screen_pinned");
@@ -211,8 +214,8 @@
         if ((flags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0) {
             str.add("a11y_long_click");
         }
-        if ((flags & SYSUI_STATE_TRACING_ENABLED) != 0) {
-            str.add("tracing");
+        if ((flags & SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION) != 0) {
+            str.add("disable_gesture_split_invocation");
         }
         if ((flags & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0) {
             str.add("asst_gesture_constrain");
@@ -265,6 +268,9 @@
         if ((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY) != 0) {
             str.add("keygrd_going_away");
         }
+        if ((flags & SYSUI_STATE_SHORTCUT_HELPER_SHOWING) != 0) {
+            str.add("shortcut_helper_showing");
+        }
 
         return str.toString();
     }
@@ -285,13 +291,13 @@
      * Returns whether the specified sysui state is such that the assistant gesture should be
      * disabled.
      */
-    public static boolean isAssistantGestureDisabled(int sysuiStateFlags) {
+    public static boolean isAssistantGestureDisabled(long sysuiStateFlags) {
         if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) {
             sysuiStateFlags &= ~SYSUI_STATE_NAV_BAR_HIDDEN;
         }
         // Disable when in quick settings, screen pinning, immersive, the bouncer is showing, 
         // or search is disabled
-        int disableFlags = SYSUI_STATE_SCREEN_PINNING
+        long disableFlags = SYSUI_STATE_SCREEN_PINNING
                 | SYSUI_STATE_NAV_BAR_HIDDEN
                 | SYSUI_STATE_BOUNCER_SHOWING
                 | SYSUI_STATE_SEARCH_DISABLED
@@ -313,7 +319,7 @@
      * Returns whether the specified sysui state is such that the back gesture should be
      * disabled.
      */
-    public static boolean isBackGestureDisabled(int sysuiStateFlags, boolean forTrackpad) {
+    public static boolean isBackGestureDisabled(long sysuiStateFlags, boolean forTrackpad) {
         // Always allow when the bouncer/global actions/voice session is showing (even on top of
         // the keyguard)
         if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0
@@ -328,9 +334,9 @@
         return (sysuiStateFlags & getBackGestureDisabledMask(forTrackpad)) != 0;
     }
 
-    private static int getBackGestureDisabledMask(boolean forTrackpad) {
+    private static long getBackGestureDisabledMask(boolean forTrackpad) {
         // Disable when in immersive, or the notifications are interactive
-        int disableFlags = SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
+        long disableFlags = SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
         if (!forTrackpad) {
             disableFlags |= SYSUI_STATE_NAV_BAR_HIDDEN;
         }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
index a6e04ce..bbf4698 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
@@ -42,7 +42,7 @@
         try {
             final TaskSnapshot snapshot = mAnimationController.screenshotTask(taskId);
             if (snapshot != null) {
-                return new ThumbnailData(snapshot);
+                return ThumbnailData.fromSnapshot(snapshot);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to screenshot task", e);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
index 473719fa..cf8ec62 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -351,7 +351,7 @@
                     case ON_TASK_SNAPSHOT_CHANGED: {
                         Trace.beginSection("onTaskSnapshotChanged");
                         final TaskSnapshot snapshot = (TaskSnapshot) msg.obj;
-                        final ThumbnailData thumbnail = new ThumbnailData(snapshot);
+                        final ThumbnailData thumbnail = ThumbnailData.fromSnapshot(snapshot);
                         boolean snapshotConsumed = false;
                         for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
                             boolean consumed = mTaskStackListeners.get(i).onTaskSnapshotChanged(
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 460779c..3f3bb0b 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -42,7 +42,9 @@
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -432,6 +434,7 @@
                         listenForDozeAmountTransition(this)
                         listenForAnyStateToAodTransition(this)
                         listenForAnyStateToLockscreenTransition(this)
+                        listenForAnyStateToDozingTransition(this)
                     } else {
                         listenForDozeAmount(this)
                     }
@@ -542,10 +545,10 @@
     internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
         return scope.launch {
             merge(
-                    keyguardTransitionInteractor.transition(AOD, LOCKSCREEN).map { step ->
-                        step.copy(value = 1f - step.value)
+                    keyguardTransitionInteractor.transition(Edge.create(AOD, LOCKSCREEN)).map {
+                        it.copy(value = 1f - it.value)
                     },
-                    keyguardTransitionInteractor.transition(LOCKSCREEN, AOD),
+                    keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, AOD)),
                 ).filter {
                     it.transitionState != TransitionState.FINISHED
                 }
@@ -578,6 +581,21 @@
         }
     }
 
+    /**
+     * When keyguard is displayed due to pulsing notifications when AOD is off,
+     * we should make sure clock is in dozing state instead of LS state
+     */
+    @VisibleForTesting
+    internal fun listenForAnyStateToDozingTransition(scope: CoroutineScope): Job {
+        return scope.launch {
+            keyguardTransitionInteractor
+                    .transitionStepsToState(DOZING)
+                    .filter { it.transitionState == TransitionState.FINISHED }
+                    .collect { handleDoze(1f) }
+        }
+    }
+
+
     @VisibleForTesting
     internal fun listenForDozing(scope: CoroutineScope): Job {
         return scope.launch {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 91fb688..42838ae 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -35,6 +35,7 @@
 
 import android.app.ActivityManager;
 import android.app.admin.DevicePolicyManager;
+import android.app.admin.flags.Flags;
 import android.content.Intent;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
@@ -134,6 +135,7 @@
     private final BouncerMessageInteractor mBouncerMessageInteractor;
     private int mTranslationY;
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    private final DevicePolicyManager mDevicePolicyManager;
     // Whether the volume keys should be handled by keyguard. If true, then
     // they will be handled here for specific media types such as music, otherwise
     // the audio service will bring up the volume dialog.
@@ -324,7 +326,8 @@
             }
 
             if (KeyguardWmStateRefactor.isEnabled()) {
-                mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+                mKeyguardTransitionInteractor.startDismissKeyguardTransition(
+                        "KeyguardSecurityContainerController#finish");
             }
         }
 
@@ -460,6 +463,7 @@
             SelectedUserInteractor selectedUserInteractor,
             DeviceProvisionedController deviceProvisionedController,
             FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
+            DevicePolicyManager devicePolicyManager,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
             Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor,
             Provider<DeviceEntryInteractor> deviceEntryInteractor
@@ -495,6 +499,7 @@
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
         mDeviceProvisionedController = deviceProvisionedController;
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
+        mDevicePolicyManager = devicePolicyManager;
     }
 
     @Override
@@ -1105,35 +1110,23 @@
 
         if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts);
 
-        final DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager();
         final int failedAttemptsBeforeWipe =
-                dpm.getMaximumFailedPasswordsForWipe(null, userId);
+                mDevicePolicyManager.getMaximumFailedPasswordsForWipe(null, userId);
 
         final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0
                 ? (failedAttemptsBeforeWipe - failedAttempts)
                 : Integer.MAX_VALUE; // because DPM returns 0 if no restriction
         if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) {
-            // The user has installed a DevicePolicyManager that requests a user/profile to be wiped
-            // N attempts. Once we get below the grace period, we post this dialog every time as a
-            // clear warning until the deletion fires.
-            // Check which profile has the strictest policy for failed password attempts
-            final int expiringUser = dpm.getProfileWithMinimumFailedPasswordsForWipe(userId);
-            int userType = USER_TYPE_PRIMARY;
-            if (expiringUser == userId) {
-                // TODO: http://b/23522538
-                if (expiringUser != UserHandle.USER_SYSTEM) {
-                    userType = USER_TYPE_SECONDARY_USER;
-                }
-            } else if (expiringUser != UserHandle.USER_NULL) {
-                userType = USER_TYPE_WORK_PROFILE;
-            } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY
-            if (remainingBeforeWipe > 0) {
-                mView.showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe, userType);
-            } else {
-                // Too many attempts. The device will be wiped shortly.
-                Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!");
-                mView.showWipeDialog(failedAttempts, userType);
-            }
+            // The user has installed a DevicePolicyManager that requests a
+            // user/profile to be wiped N attempts. Once we get below the grace period,
+            // we post this dialog every time as a clear warning until the deletion
+            // fires. Check which profile has the strictest policy for failed password
+            // attempts.
+            final int expiringUser =
+                    mDevicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(userId);
+            Integer mainUser = mSelectedUserInteractor.getMainUserId();
+            showMessageForFailedUnlockAttempt(
+                    userId, expiringUser, mainUser, remainingBeforeWipe, failedAttempts);
         }
         mLockPatternUtils.reportFailedPasswordAttempt(userId);
         if (timeoutMs > 0) {
@@ -1145,6 +1138,35 @@
         }
     }
 
+    @VisibleForTesting
+    void showMessageForFailedUnlockAttempt(int userId, int expiringUserId, Integer mainUserId,
+            int remainingBeforeWipe, int failedAttempts) {
+        int userType = USER_TYPE_PRIMARY;
+        if (expiringUserId == userId) {
+            int primaryUser = UserHandle.USER_SYSTEM;
+            if (Flags.headlessSingleUserFixes()) {
+                if (mainUserId != null) {
+                    primaryUser = mainUserId;
+                }
+            }
+            // TODO: http://b/23522538
+            if (expiringUserId != primaryUser) {
+                userType = USER_TYPE_SECONDARY_USER;
+            }
+        } else if (expiringUserId != UserHandle.USER_NULL) {
+            userType = USER_TYPE_WORK_PROFILE;
+        } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY
+        if (remainingBeforeWipe > 0) {
+            mView.showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe,
+                    userType);
+        } else {
+            // Too many attempts. The device will be wiped shortly.
+            Slog.i(TAG, "Too many unlock attempts; user " + expiringUserId
+                    + " will be wiped!");
+            mView.showWipeDialog(failedAttempts, userType);
+        }
+    }
+
     private void getCurrentSecurityController(
             KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedCallback) {
         mSecurityViewFlipperController
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index 57c1fd0..42896a4 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -569,6 +569,11 @@
         return true;
     }
 
+    /** Finish the current expand motion without accounting for velocity. */
+    public void finishExpanding() {
+        finishExpanding(false, 0);
+    }
+
     /**
      * Finish the current expand motion
      * @param forceAbort whether the expansion should be forcefully aborted and returned to the old
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
index 35f9344..004d5db 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.accessibility.data.repository.ColorCorrectionRepositoryImpl
 import com.android.systemui.accessibility.data.repository.ColorInversionRepository
 import com.android.systemui.accessibility.data.repository.ColorInversionRepositoryImpl
+import com.android.systemui.accessibility.data.repository.OneHandedModeRepository
+import com.android.systemui.accessibility.data.repository.OneHandedModeRepositoryImpl
 import com.android.systemui.accessibility.qs.QSAccessibilityModule
 import dagger.Binds
 import dagger.Module
@@ -34,6 +36,8 @@
     @Binds
     fun colorInversionRepository(impl: ColorInversionRepositoryImpl): ColorInversionRepository
 
+    @Binds fun oneHandedModeRepository(impl: OneHandedModeRepositoryImpl): OneHandedModeRepository
+
     @Binds
     fun accessibilityQsShortcutsRepository(
         impl: AccessibilityQsShortcutsRepositoryImpl
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index 177d933..35c2024 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -30,6 +30,8 @@
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.SurfaceControl;
@@ -41,6 +43,8 @@
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
 import android.window.InputTransferToken;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.CoreStartable;
@@ -69,6 +73,9 @@
 public class Magnification implements CoreStartable, CommandQueue.Callbacks {
     private static final String TAG = "Magnification";
 
+    @VisibleForTesting static final int DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS = 300;
+    private static final int MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL = 1;
+
     private final ModeSwitchesController mModeSwitchesController;
     private final Context mContext;
     private final Handler mHandler;
@@ -209,8 +216,26 @@
             SysUiState sysUiState, OverviewProxyService overviewProxyService,
             SecureSettings secureSettings, DisplayTracker displayTracker,
             DisplayManager displayManager, AccessibilityLogger a11yLogger) {
+        this(context, mainHandler.getLooper(), executor, commandQueue,
+                modeSwitchesController, sysUiState, overviewProxyService, secureSettings,
+                displayTracker, displayManager, a11yLogger);
+    }
+
+    @VisibleForTesting
+    public Magnification(Context context, Looper looper, @Main Executor executor,
+            CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
+            SysUiState sysUiState, OverviewProxyService overviewProxyService,
+            SecureSettings secureSettings, DisplayTracker displayTracker,
+            DisplayManager displayManager, AccessibilityLogger a11yLogger) {
         mContext = context;
-        mHandler = mainHandler;
+        mHandler = new Handler(looper) {
+            @Override
+            public void handleMessage(@NonNull Message msg) {
+                if (msg.what == MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL) {
+                    showMagnificationButtonInternal(msg.arg1, msg.arg2);
+                }
+            }
+        };
         mExecutor = executor;
         mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
         mCommandQueue = commandQueue;
@@ -350,6 +375,21 @@
 
     @MainThread
     void showMagnificationButton(int displayId, int magnificationMode) {
+        if (Flags.delayShowMagnificationButton()) {
+            if (mHandler.hasMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL)) {
+                return;
+            }
+            mHandler.sendMessageDelayed(
+                    mHandler.obtainMessage(
+                            MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL, displayId, magnificationMode),
+                    DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS);
+        } else {
+            showMagnificationButtonInternal(displayId, magnificationMode);
+        }
+    }
+
+    @MainThread
+    private void showMagnificationButtonInternal(int displayId, int magnificationMode) {
         // not to show mode switch button if settings panel is already showing to
         // prevent settings panel be covered by the button.
         if (isMagnificationSettingsPanelShowing(displayId)) {
@@ -360,6 +400,9 @@
 
     @MainThread
     void removeMagnificationButton(int displayId) {
+        if (Flags.delayShowMagnificationButton()) {
+            mHandler.removeMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL);
+        }
         mModeSwitchesController.removeButton(displayId);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index e66261c..5458ab1 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -240,7 +240,7 @@
     private boolean mEditSizeEnable = false;
     private boolean mSettingsPanelVisibility = false;
     @VisibleForTesting
-    WindowMagnificationSizePrefs mWindowMagnificationSizePrefs;
+    WindowMagnificationFrameSizePrefs mWindowMagnificationFrameSizePrefs;
 
     @Nullable
     private final MirrorWindowControl mMirrorWindowControl;
@@ -270,7 +270,7 @@
         mSysUiState = sysUiState;
         mScvhSupplier = scvhSupplier;
         mConfiguration = new Configuration(context.getResources().getConfiguration());
-        mWindowMagnificationSizePrefs = new WindowMagnificationSizePrefs(mContext);
+        mWindowMagnificationFrameSizePrefs = new WindowMagnificationFrameSizePrefs(mContext);
 
         final Display display = mContext.getDisplay();
         mDisplayId = mContext.getDisplayId();
@@ -457,7 +457,7 @@
 
         if (!enable) {
             // Keep the magnifier size when exiting edit mode
-            mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(
+            mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity(
                     new Size(mMagnificationFrame.width(), mMagnificationFrame.height()));
         }
     }
@@ -944,7 +944,7 @@
     }
 
     private void setMagnificationFrame(int width, int height, int centerX, int centerY) {
-        mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(new Size(width, height));
+        mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity(new Size(width, height));
 
         // Sets the initial frame area for the mirror and place it to the given center on the
         // display.
@@ -954,11 +954,11 @@
     }
 
     private Size restoreMagnificationWindowFrameSizeIfPossible() {
-        if (!mWindowMagnificationSizePrefs.isPreferenceSavedForCurrentDensity()) {
+        if (!mWindowMagnificationFrameSizePrefs.isPreferenceSavedForCurrentDensity()) {
             return getDefaultMagnificationWindowFrameSize();
         }
 
-        return mWindowMagnificationSizePrefs.getSizeForCurrentDensity();
+        return mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity();
     }
 
     private Size getDefaultMagnificationWindowFrameSize() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSizePrefs.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSizePrefs.java
rename to packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java
index a401f2a..e83e85e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSizePrefs.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java
@@ -23,14 +23,14 @@
 /**
  * Class to handle SharedPreference for window magnification size.
  */
-final class WindowMagnificationSizePrefs {
+final class WindowMagnificationFrameSizePrefs {
 
     private static final String WINDOW_MAGNIFICATION_PREFERENCES =
             "window_magnification_preferences";
     Context mContext;
     SharedPreferences mWindowMagnificationSizePreferences;
 
-    public WindowMagnificationSizePrefs(Context context) {
+    WindowMagnificationFrameSizePrefs(Context context) {
         mContext = context;
         mWindowMagnificationSizePreferences = mContext
                 .getSharedPreferences(WINDOW_MAGNIFICATION_PREFERENCES, Context.MODE_PRIVATE);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayChangeEvent.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayChangeEvent.kt
new file mode 100644
index 0000000..8f071e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayChangeEvent.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.data.model
+
+import java.time.LocalTime
+
+sealed interface NightDisplayChangeEvent {
+    data class OnAutoModeChanged(val autoMode: Int) : NightDisplayChangeEvent
+    data class OnActivatedChanged(val isActivated: Boolean) : NightDisplayChangeEvent
+    data class OnCustomStartTimeChanged(val startTime: LocalTime?) : NightDisplayChangeEvent
+    data class OnCustomEndTimeChanged(val endTime: LocalTime?) : NightDisplayChangeEvent
+    data class OnForceAutoModeChanged(val shouldForceAutoMode: Boolean) : NightDisplayChangeEvent
+    data class OnLocationEnabledChanged(val locationEnabled: Boolean) : NightDisplayChangeEvent
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayState.kt
similarity index 61%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayState.kt
index bf22563..196876e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayState.kt
@@ -14,12 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.accessibility.data.model
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import java.time.LocalTime
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+/** models the state of NightDisplayRepository */
+data class NightDisplayState(
+    val autoMode: Int = 0,
+    val isActivated: Boolean = true,
+    val startTime: LocalTime? = null,
+    val endTime: LocalTime? = null,
+    val shouldForceAutoMode: Boolean = false,
+    val locationEnabled: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt
index ae9f57f..6032f0b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt
@@ -18,7 +18,7 @@
 
 import android.view.accessibility.AccessibilityManager
 import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import dagger.Module
 import dagger.Provides
 import kotlinx.coroutines.channels.awaitClose
@@ -29,6 +29,8 @@
 interface AccessibilityRepository {
     /** @see [AccessibilityManager.isTouchExplorationEnabled] */
     val isTouchExplorationEnabled: Flow<Boolean>
+    /** @see [AccessibilityManager.isEnabled] */
+    val isEnabled: Flow<Boolean>
 
     companion object {
         operator fun invoke(a11yManager: AccessibilityManager): AccessibilityRepository =
@@ -47,6 +49,15 @@
                 awaitClose { manager.removeTouchExplorationStateChangeListener(listener) }
             }
             .distinctUntilChanged()
+
+    override val isEnabled: Flow<Boolean> =
+        conflatedCallbackFlow {
+                val listener = AccessibilityManager.AccessibilityStateChangeListener(::trySend)
+                manager.addAccessibilityStateChangeListener(listener)
+                trySend(manager.isEnabled)
+                awaitClose { manager.removeAccessibilityStateChangeListener(listener) }
+            }
+            .distinctUntilChanged()
 }
 
 @Module
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/NightDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/NightDisplayRepository.kt
new file mode 100644
index 0000000..b33746c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/NightDisplayRepository.kt
@@ -0,0 +1,198 @@
+/*
+ * 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.data.repository
+
+import android.hardware.display.ColorDisplayManager
+import android.hardware.display.NightDisplayListener
+import android.os.UserHandle
+import android.provider.Settings
+import com.android.systemui.accessibility.data.model.NightDisplayChangeEvent
+import com.android.systemui.accessibility.data.model.NightDisplayState
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.policy.LocationController
+import com.android.systemui.user.utils.UserScopedService
+import com.android.systemui.util.kotlin.isLocationEnabledFlow
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import java.time.LocalTime
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.scan
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+class NightDisplayRepository
+@Inject
+constructor(
+    @Background private val bgCoroutineContext: CoroutineContext,
+    @Application private val scope: CoroutineScope,
+    private val globalSettings: GlobalSettings,
+    private val secureSettings: SecureSettings,
+    private val nightDisplayListenerBuilder: NightDisplayListenerModule.Builder,
+    private val colorDisplayManagerUserScopedService: UserScopedService<ColorDisplayManager>,
+    private val locationController: LocationController,
+) {
+    private val stateFlowUserMap = mutableMapOf<Int, Flow<NightDisplayState>>()
+
+    fun nightDisplayState(user: UserHandle): Flow<NightDisplayState> =
+        stateFlowUserMap.getOrPut(user.identifier) {
+            return merge(
+                    colorDisplayManagerChangeEventFlow(user),
+                    shouldForceAutoMode(user).map {
+                        NightDisplayChangeEvent.OnForceAutoModeChanged(it)
+                    },
+                    locationController.isLocationEnabledFlow().map {
+                        NightDisplayChangeEvent.OnLocationEnabledChanged(it)
+                    }
+                )
+                .scan(initialState(user)) { state, event ->
+                    when (event) {
+                        is NightDisplayChangeEvent.OnActivatedChanged ->
+                            state.copy(isActivated = event.isActivated)
+                        is NightDisplayChangeEvent.OnAutoModeChanged ->
+                            state.copy(autoMode = event.autoMode)
+                        is NightDisplayChangeEvent.OnCustomStartTimeChanged ->
+                            state.copy(startTime = event.startTime)
+                        is NightDisplayChangeEvent.OnCustomEndTimeChanged ->
+                            state.copy(endTime = event.endTime)
+                        is NightDisplayChangeEvent.OnForceAutoModeChanged ->
+                            state.copy(shouldForceAutoMode = event.shouldForceAutoMode)
+                        is NightDisplayChangeEvent.OnLocationEnabledChanged ->
+                            state.copy(locationEnabled = event.locationEnabled)
+                    }
+                }
+                .conflate()
+                .onStart { emit(initialState(user)) }
+                .flowOn(bgCoroutineContext)
+                .stateIn(scope, SharingStarted.WhileSubscribed(), NightDisplayState())
+        }
+
+    /** Track changes in night display enabled state and its auto mode */
+    private fun colorDisplayManagerChangeEventFlow(user: UserHandle) = callbackFlow {
+        val nightDisplayListener = nightDisplayListenerBuilder.setUser(user.identifier).build()
+        val nightDisplayCallback =
+            object : NightDisplayListener.Callback {
+                override fun onActivated(activated: Boolean) {
+                    trySend(NightDisplayChangeEvent.OnActivatedChanged(activated))
+                }
+
+                override fun onAutoModeChanged(autoMode: Int) {
+                    trySend(NightDisplayChangeEvent.OnAutoModeChanged(autoMode))
+                }
+
+                override fun onCustomStartTimeChanged(startTime: LocalTime?) {
+                    trySend(NightDisplayChangeEvent.OnCustomStartTimeChanged(startTime))
+                }
+
+                override fun onCustomEndTimeChanged(endTime: LocalTime?) {
+                    trySend(NightDisplayChangeEvent.OnCustomEndTimeChanged(endTime))
+                }
+            }
+        nightDisplayListener.setCallback(nightDisplayCallback)
+        awaitClose { nightDisplayListener.setCallback(null) }
+    }
+
+    /** @return true when the option to force auto mode is available and a value has not been set */
+    private fun shouldForceAutoMode(userHandle: UserHandle): Flow<Boolean> =
+        combine(isForceAutoModeAvailable, isDisplayAutoModeRawNotSet(userHandle)) {
+            isForceAutoModeAvailable,
+            isDisplayAutoModeRawNotSet,
+            ->
+            isForceAutoModeAvailable && isDisplayAutoModeRawNotSet
+        }
+
+    private val isForceAutoModeAvailable: Flow<Boolean> =
+        globalSettings
+            .observerFlow(IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME)
+            .onStart { emit(Unit) }
+            .map {
+                globalSettings.getString(IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME) ==
+                    NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE
+            }
+            .distinctUntilChanged()
+
+    /** Inspired by [ColorDisplayService.getNightDisplayAutoModeRawInternal] */
+    private fun isDisplayAutoModeRawNotSet(userHandle: UserHandle): Flow<Boolean> =
+        if (userHandle.identifier == UserHandle.USER_NULL) {
+                flowOf(IS_AUTO_MODE_RAW_NOT_SET_DEFAULT)
+            } else {
+                secureSettings
+                    .observerFlow(userHandle.identifier, DISPLAY_AUTO_MODE_RAW_SETTING_NAME)
+                    .onStart { emit(Unit) }
+                    .map { isNightDisplayAutoModeRawSettingNotSet(userHandle.identifier) }
+            }
+            .distinctUntilChanged()
+
+    suspend fun setNightDisplayAutoMode(autoMode: Int, user: UserHandle) {
+        withContext(bgCoroutineContext) {
+            colorDisplayManagerUserScopedService.forUser(user).nightDisplayAutoMode = autoMode
+        }
+    }
+
+    suspend fun setNightDisplayActivated(activated: Boolean, user: UserHandle) {
+        withContext(bgCoroutineContext) {
+            colorDisplayManagerUserScopedService.forUser(user).isNightDisplayActivated = activated
+        }
+    }
+
+    private fun initialState(user: UserHandle): NightDisplayState {
+        val colorDisplayManager = colorDisplayManagerUserScopedService.forUser(user)
+        return NightDisplayState(
+            colorDisplayManager.nightDisplayAutoMode,
+            colorDisplayManager.isNightDisplayActivated,
+            colorDisplayManager.nightDisplayCustomStartTime,
+            colorDisplayManager.nightDisplayCustomEndTime,
+            globalSettings.getString(IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME) ==
+                NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE &&
+                isNightDisplayAutoModeRawSettingNotSet(user.identifier),
+            locationController.isLocationEnabled,
+        )
+    }
+
+    private fun isNightDisplayAutoModeRawSettingNotSet(userId: Int): Boolean {
+        return secureSettings.getIntForUser(
+            DISPLAY_AUTO_MODE_RAW_SETTING_NAME,
+            NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET,
+            userId
+        ) == NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET
+    }
+
+    private companion object {
+        const val NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET = -1
+        const val NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE = "1"
+        const val IS_AUTO_MODE_RAW_NOT_SET_DEFAULT = true
+        const val IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME =
+            Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE
+        const val DISPLAY_AUTO_MODE_RAW_SETTING_NAME = Settings.Secure.NIGHT_DISPLAY_AUTO_MODE
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepository.kt
new file mode 100644
index 0000000..d921025
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepository.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.data.repository
+
+import android.os.UserHandle
+import android.provider.Settings
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/** Provides data related to one handed mode. */
+interface OneHandedModeRepository {
+    /** Observable for whether one handed mode is enabled */
+    fun isEnabled(userHandle: UserHandle): Flow<Boolean>
+
+    /** Sets one handed mode enabled state. */
+    suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean
+}
+
+@SysUISingleton
+class OneHandedModeRepositoryImpl
+@Inject
+constructor(
+    @Background private val bgCoroutineContext: CoroutineContext,
+    @Application private val scope: CoroutineScope,
+    private val secureSettings: SecureSettings,
+) : OneHandedModeRepository {
+
+    private val userMap = mutableMapOf<Int, Flow<Boolean>>()
+
+    override fun isEnabled(userHandle: UserHandle): Flow<Boolean> =
+        userMap.getOrPut(userHandle.identifier) {
+            secureSettings
+                .observerFlow(userHandle.identifier, SETTING_NAME)
+                .onStart { emit(Unit) }
+                .map {
+                    secureSettings.getIntForUser(SETTING_NAME, DISABLED, userHandle.identifier) ==
+                        ENABLED
+                }
+                .distinctUntilChanged()
+                .flowOn(bgCoroutineContext)
+                .stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_VALUE)
+        }
+
+    override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean =
+        withContext(bgCoroutineContext) {
+            secureSettings.putIntForUser(
+                SETTING_NAME,
+                if (isEnabled) ENABLED else DISABLED,
+                userHandle.identifier
+            )
+        }
+
+    companion object {
+        private const val SETTING_NAME = Settings.Secure.ONE_HANDED_MODE_ENABLED
+        private const val DISABLED = 0
+        private const val ENABLED = 1
+        private const val DEFAULT_VALUE = false
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt
index 968ce0d..93b624a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt
@@ -28,6 +28,8 @@
     private val a11yRepo: AccessibilityRepository,
 ) {
     /** @see [android.view.accessibility.AccessibilityManager.isTouchExplorationEnabled] */
-    val isTouchExplorationEnabled: Flow<Boolean>
-        get() = a11yRepo.isTouchExplorationEnabled
+    val isTouchExplorationEnabled: Flow<Boolean> = a11yRepo.isTouchExplorationEnabled
+
+    /** @see [android.view.accessibility.AccessibilityManager.isEnabled] */
+    val isEnabled: Flow<Boolean> = a11yRepo.isEnabled
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index 1f04599..d5e911e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -37,7 +37,6 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Flags;
 
 import java.util.HashMap;
 
@@ -339,15 +338,11 @@
         mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ true);
         final PointF position = mMenuView.getMenuPosition();
         final PointF tuckedPosition = getTuckedMenuPosition();
-        if (Flags.floatingMenuAnimatedTuck()) {
-            flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_X,
-                    Math.signum(tuckedPosition.x - position.x) * ESCAPE_VELOCITY,
-                    FLING_FRICTION_SCALAR,
-                    createDefaultSpringForce(),
-                    tuckedPosition.x);
-        } else {
-            moveToPosition(tuckedPosition);
-        }
+        flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_X,
+                Math.signum(tuckedPosition.x - position.x) * ESCAPE_VELOCITY,
+                FLING_FRICTION_SCALAR,
+                createDefaultSpringForce(),
+                tuckedPosition.x);
 
         // Keep the touch region let users could click extra space to pop up the menu view
         // from the screen edge
@@ -359,23 +354,19 @@
     void moveOutEdgeAndShow() {
         mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
 
-        if (Flags.floatingMenuAnimatedTuck()) {
-            PointF position = mMenuView.getMenuPosition();
-            springMenuWith(DynamicAnimation.TRANSLATION_X,
-                    createDefaultSpringForce(),
-                    0,
-                    position.x,
-                    true
-            );
-            springMenuWith(DynamicAnimation.TRANSLATION_Y,
-                    createDefaultSpringForce(),
-                    0,
-                    position.y,
-                    true
-            );
-        } else {
-            mMenuView.onPositionChanged();
-        }
+        PointF position = mMenuView.getMenuPosition();
+        springMenuWith(DynamicAnimation.TRANSLATION_X,
+                createDefaultSpringForce(),
+                0,
+                position.x,
+                true
+        );
+        springMenuWith(DynamicAnimation.TRANSLATION_Y,
+                createDefaultSpringForce(),
+                0,
+                position.y,
+                true
+        );
 
         mMenuView.onEdgeChangedIfNeeded();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index be75e10..9d9e7df 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -321,22 +321,6 @@
         if (mMoveToTuckedListener != null) {
             mMoveToTuckedListener.onMoveToTuckedChanged(isMoveToTucked);
         }
-
-        if (!Flags.floatingMenuAnimatedTuck()) {
-            if (isMoveToTucked) {
-                final float halfWidth = getMenuWidth() / 2.0f;
-                final boolean isOnLeftSide = mMenuAnimationController.isOnLeftSide();
-                final Rect clipBounds = new Rect(
-                        (int) (!isOnLeftSide ? 0 : halfWidth),
-                        0,
-                        (int) (!isOnLeftSide ? halfWidth : getMenuWidth()),
-                        getMenuHeight()
-                );
-                setClipBounds(clipBounds);
-            } else {
-                setClipBounds(null);
-            }
-        }
     }
 
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 6dce1bb..0c67c50 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -322,9 +322,8 @@
         }
         addView(mMessageView, LayerIndex.MESSAGE_VIEW);
 
-        if (Flags.floatingMenuAnimatedTuck()) {
-            setClipChildren(true);
-        }
+        setClipChildren(true);
+
         setClickable(false);
         setFocusable(false);
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
@@ -476,10 +475,8 @@
             mMenuAnimationController.startTuckedAnimationPreview();
         }
 
-        if (Flags.floatingMenuAnimatedTuck()) {
-            if (!mMenuView.isMoveToTucked()) {
-                setClipBounds(null);
-            }
+        if (!mMenuView.isMoveToTucked()) {
+            setClipBounds(null);
         }
         mMenuView.onArrivalAtPosition(false);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index 99be762..ed9597d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -41,6 +41,14 @@
 import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionTileDataInteractor
 import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionUserActionInteractor
 import com.android.systemui.qs.tiles.impl.inversion.domain.model.ColorInversionTileModel
+import com.android.systemui.qs.tiles.impl.night.domain.interactor.NightDisplayTileDataInteractor
+import com.android.systemui.qs.tiles.impl.night.domain.interactor.NightDisplayTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.qs.tiles.impl.night.ui.NightDisplayTileMapper
+import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileDataInteractor
+import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
+import com.android.systemui.qs.tiles.impl.onehanded.ui.OneHandedModeTileMapper
 import com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor.ReduceBrightColorsTileDataInteractor
 import com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor.ReduceBrightColorsTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
@@ -113,6 +121,7 @@
         const val FONT_SCALING_TILE_SPEC = "font_scaling"
         const val REDUCE_BRIGHTNESS_TILE_SPEC = "reduce_brightness"
         const val ONE_HANDED_TILE_SPEC = "onehanded"
+        const val NIGHT_DISPLAY_TILE_SPEC = "night"
 
         @Provides
         @IntoMap
@@ -256,5 +265,60 @@
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
             )
+
+        /** Inject One Handed Mode Tile into tileViewModelMap in QSModule. */
+        @Provides
+        @IntoMap
+        @StringKey(ONE_HANDED_TILE_SPEC)
+        fun provideOneHandedModeTileViewModel(
+            factory: QSTileViewModelFactory.Static<OneHandedModeTileModel>,
+            mapper: OneHandedModeTileMapper,
+            stateInteractor: OneHandedModeTileDataInteractor,
+            userActionInteractor: OneHandedModeTileUserActionInteractor
+        ): QSTileViewModel =
+            if (Flags.qsNewTilesFuture())
+                factory.create(
+                    TileSpec.create(ONE_HANDED_TILE_SPEC),
+                    userActionInteractor,
+                    stateInteractor,
+                    mapper,
+                )
+            else StubQSTileViewModel
+
+        @Provides
+        @IntoMap
+        @StringKey(NIGHT_DISPLAY_TILE_SPEC)
+        fun provideNightDisplayTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(NIGHT_DISPLAY_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.qs_nightlight_icon_off,
+                        labelRes = R.string.quick_settings_night_display_label,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+            )
+
+        /**
+         * Inject NightDisplay Tile into tileViewModelMap in QSModule. The tile is hidden behind a
+         * flag.
+         */
+        @Provides
+        @IntoMap
+        @StringKey(NIGHT_DISPLAY_TILE_SPEC)
+        fun provideNightDisplayTileViewModel(
+            factory: QSTileViewModelFactory.Static<NightDisplayTileModel>,
+            mapper: NightDisplayTileMapper,
+            stateInteractor: NightDisplayTileDataInteractor,
+            userActionInteractor: NightDisplayTileUserActionInteractor
+        ): QSTileViewModel =
+            if (Flags.qsNewTilesFuture())
+                factory.create(
+                    TileSpec.create(NIGHT_DISPLAY_TILE_SPEC),
+                    userActionInteractor,
+                    stateInteractor,
+                    mapper,
+                )
+            else StubQSTileViewModel
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
index 9c7fc9d..9ef9938 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
@@ -23,8 +23,7 @@
 import android.view.GestureDetector;
 import android.view.MotionEvent;
 
-import androidx.annotation.NonNull;
-
+import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 
 import java.util.Optional;
@@ -38,34 +37,29 @@
  */
 public class ShadeTouchHandler implements TouchHandler {
     private final Optional<CentralSurfaces> mSurfaces;
+    private final ShadeViewController mShadeViewController;
     private final int mInitiationHeight;
 
-    /**
-     * Tracks whether or not we are capturing a given touch. Will be null before and after a touch.
-     */
-    private Boolean mCapture;
-
     @Inject
     ShadeTouchHandler(Optional<CentralSurfaces> centralSurfaces,
+            ShadeViewController shadeViewController,
             @Named(NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT) int initiationHeight) {
         mSurfaces = centralSurfaces;
+        mShadeViewController = shadeViewController;
         mInitiationHeight = initiationHeight;
     }
 
     @Override
     public void onSessionStart(TouchSession session) {
-        if (mSurfaces.isEmpty()) {
+        if (mSurfaces.map(CentralSurfaces::isBouncerShowing).orElse(false)) {
             session.pop();
             return;
         }
 
-        session.registerCallback(() -> mCapture = null);
-
         session.registerInputListener(ev -> {
+            mShadeViewController.handleExternalTouch((MotionEvent) ev);
+
             if (ev instanceof MotionEvent) {
-                if (mCapture != null && mCapture) {
-                    mSurfaces.get().handleExternalShadeWindowTouch((MotionEvent) ev);
-                }
                 if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
                     session.pop();
                 }
@@ -74,25 +68,15 @@
 
         session.registerGestureListener(new GestureDetector.SimpleOnGestureListener() {
             @Override
-            public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX,
+            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
                     float distanceY) {
-                if (mCapture == null) {
-                    // Only capture swipes that are going downwards.
-                    mCapture = Math.abs(distanceY) > Math.abs(distanceX) && distanceY < 0;
-                    if (mCapture) {
-                        // Send the initial touches over, as the input listener has already
-                        // processed these touches.
-                        mSurfaces.get().handleExternalShadeWindowTouch(e1);
-                        mSurfaces.get().handleExternalShadeWindowTouch(e2);
-                    }
-                }
-                return mCapture;
+                return true;
             }
 
             @Override
-            public boolean onFling(MotionEvent e1, @NonNull MotionEvent e2, float velocityX,
+            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                     float velocityY) {
-                return mCapture;
+                return true;
             }
         });
     }
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 5df7fc9..fcba425 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.authentication.domain.interactor
 
+import android.app.admin.flags.Flags
 import android.os.UserHandle
 import com.android.internal.widget.LockPatternUtils
 import com.android.internal.widget.LockPatternView
@@ -288,9 +289,15 @@
     private suspend fun getWipeTarget(): WipeTarget {
         // Check which profile has the strictest policy for failed authentication attempts.
         val userToBeWiped = repository.getProfileWithMinFailedUnlockAttemptsForWipe()
+        val primaryUser =
+            if (Flags.headlessSingleUserFixes()) {
+                selectedUserInteractor.getMainUserId() ?: UserHandle.USER_SYSTEM
+            } else {
+                UserHandle.USER_SYSTEM
+            }
         return when (userToBeWiped) {
             selectedUserInteractor.getSelectedUserId() ->
-                if (userToBeWiped == UserHandle.USER_SYSTEM) {
+                if (userToBeWiped == primaryUser) {
                     WipeTarget.WholeDevice
                 } else {
                     WipeTarget.User
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 5ba0b2d..298c0f7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -73,9 +73,9 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.biometrics.AuthController.ScaleFactorProvider;
-import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor;
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
 import com.android.systemui.biometrics.shared.model.BiometricModalities;
+import com.android.systemui.biometrics.shared.model.PromptKind;
 import com.android.systemui.biometrics.ui.BiometricPromptLayout;
 import com.android.systemui.biometrics.ui.CredentialView;
 import com.android.systemui.biometrics.ui.binder.BiometricViewBinder;
@@ -149,7 +149,6 @@
     private final CoroutineScope mApplicationCoroutineScope;
 
     // TODO(b/287311775): these should be migrated out once ready
-    private final Provider<PromptCredentialInteractor> mPromptCredentialInteractor;
     private final @NonNull Provider<PromptSelectorInteractor> mPromptSelectorInteractorProvider;
     // TODO(b/287311775): these should be migrated out of the view
     private final Provider<CredentialViewModel> mCredentialViewModelProvider;
@@ -310,7 +309,6 @@
             @NonNull UserManager userManager,
             @NonNull LockPatternUtils lockPatternUtils,
             @NonNull InteractionJankMonitor jankMonitor,
-            @NonNull Provider<PromptCredentialInteractor> promptCredentialInteractor,
             @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractor,
             @NonNull PromptViewModel promptViewModel,
             @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
@@ -318,7 +316,7 @@
             @NonNull VibratorHelper vibratorHelper) {
         this(config, applicationCoroutineScope, fpProps, faceProps,
                 wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
-                jankMonitor, promptSelectorInteractor, promptCredentialInteractor, promptViewModel,
+                jankMonitor, promptSelectorInteractor, promptViewModel,
                 credentialViewModelProvider, new Handler(Looper.getMainLooper()), bgExecutor,
                 vibratorHelper);
     }
@@ -334,7 +332,6 @@
             @NonNull LockPatternUtils lockPatternUtils,
             @NonNull InteractionJankMonitor jankMonitor,
             @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractorProvider,
-            @NonNull Provider<PromptCredentialInteractor> credentialInteractor,
             @NonNull PromptViewModel promptViewModel,
             @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
             @NonNull Handler mainHandler,
@@ -357,13 +354,19 @@
         mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
         mBiometricCallback = new BiometricCallback();
 
+        mFpProps = fpProps;
+        mFaceProps = faceProps;
+        final BiometricModalities biometricModalities = new BiometricModalities(
+                Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
+                Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds));
+
         mPromptSelectorInteractorProvider = promptSelectorInteractorProvider;
-        mPromptSelectorInteractorProvider.get().setShouldShowBpWithoutIconForCredential(
-                config.mPromptInfo);
+        mPromptSelectorInteractorProvider.get().setPrompt(mConfig.mPromptInfo, mEffectiveUserId,
+                biometricModalities, mConfig.mOperationId, mConfig.mOpPackageName,
+                false /*onSwitchToCredential*/);
 
         final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
-        if (constraintBp() && (Utils.isBiometricAllowed(config.mPromptInfo)
-                || mPromptViewModel.getShowBpWithoutIconForCredential().getValue())) {
+        if (constraintBp() && mPromptViewModel.getPromptKind().getValue().isBiometric()) {
             mLayout = (ConstraintLayout) layoutInflater.inflate(
                     R.layout.biometric_prompt_constraint_layout, this, false /* attachToRoot */);
         } else {
@@ -397,10 +400,7 @@
         mPanelController = new AuthPanelController(mContext, mPanelView);
         mBackgroundExecutor = bgExecutor;
         mInteractionJankMonitor = jankMonitor;
-        mPromptCredentialInteractor = credentialInteractor;
         mCredentialViewModelProvider = credentialViewModelProvider;
-        mFpProps = fpProps;
-        mFaceProps = faceProps;
 
         showPrompt(config, layoutInflater, promptViewModel,
                 Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
@@ -429,11 +429,12 @@
             @Nullable FaceSensorPropertiesInternal faceProps,
             @NonNull VibratorHelper vibratorHelper
     ) {
-        if (Utils.isBiometricAllowed(config.mPromptInfo)
-                || mPromptViewModel.getShowBpWithoutIconForCredential().getValue()) {
+        if (mPromptViewModel.getPromptKind().getValue().isBiometric()) {
             addBiometricView(config, layoutInflater, viewModel, fpProps, faceProps, vibratorHelper);
-        } else if (constraintBp() && Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) {
-            addCredentialView(true, false);
+        } else if (mPromptViewModel.getPromptKind().getValue().isCredential()) {
+            if (constraintBp()) {
+                addCredentialView(true, false);
+            }
         } else {
             mPromptSelectorInteractorProvider.get().resetPrompt();
         }
@@ -444,12 +445,6 @@
             @Nullable FingerprintSensorPropertiesInternal fpProps,
             @Nullable FaceSensorPropertiesInternal faceProps,
             @NonNull VibratorHelper vibratorHelper) {
-        mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication(
-                config.mPromptInfo,
-                config.mUserId,
-                config.mOperationId,
-                new BiometricModalities(fpProps, faceProps),
-                config.mOpPackageName);
 
         if (constraintBp()) {
             mBiometricView = BiometricViewBinder.bind(mLayout, viewModel, null,
@@ -500,34 +495,24 @@
     private void addCredentialView(boolean animatePanel, boolean animateContents) {
         final LayoutInflater factory = LayoutInflater.from(mContext);
 
-        @Utils.CredentialType final int credentialType = Utils.getCredentialType(
-                mLockPatternUtils, mEffectiveUserId);
-
-        switch (credentialType) {
-            case Utils.CREDENTIAL_PATTERN:
-                mCredentialView = factory.inflate(
-                        R.layout.auth_credential_pattern_view, null, false);
-                break;
-            case Utils.CREDENTIAL_PIN:
-                mCredentialView = factory.inflate(R.layout.auth_credential_pin_view, null, false);
-                break;
-            case Utils.CREDENTIAL_PASSWORD:
-                mCredentialView = factory.inflate(
-                        R.layout.auth_credential_password_view, null, false);
-                break;
-            default:
-                throw new IllegalStateException("Unknown credential type: " + credentialType);
+        PromptKind credentialType = Utils.getCredentialType(mLockPatternUtils, mEffectiveUserId);
+        final int layoutResourceId;
+        if (credentialType instanceof PromptKind.Pattern) {
+            layoutResourceId = R.layout.auth_credential_pattern_view;
+        } else if (credentialType instanceof PromptKind.Pin) {
+            layoutResourceId = R.layout.auth_credential_pin_view;
+        } else if (credentialType instanceof PromptKind.Password) {
+            layoutResourceId = R.layout.auth_credential_password_view;
+        } else {
+            throw new IllegalStateException("Unknown credential type: " + credentialType);
         }
+        mCredentialView = factory.inflate(layoutResourceId, null, false);
 
         // The background is used for detecting taps / cancelling authentication. Since the
         // credential view is full-screen and should not be canceled from background taps,
         // disable it.
         mBackgroundView.setOnClickListener(null);
         mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
-
-        mPromptSelectorInteractorProvider.get().useCredentialsForAuthentication(
-                mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId,
-                mConfig.mOpPackageName);
         final CredentialViewModel vm = mCredentialViewModelProvider.get();
         vm.setAnimateContents(animateContents);
         ((CredentialView) mCredentialView).init(vm, this, mPanelController, animatePanel,
@@ -562,10 +547,9 @@
                 () -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED));
         if (constraintBp()) {
             // Do nothing on attachment with constraintLayout
-        } else if (Utils.isBiometricAllowed(mConfig.mPromptInfo)
-                || mPromptViewModel.getShowBpWithoutIconForCredential().getValue()) {
+        } else if (mPromptViewModel.getPromptKind().getValue().isBiometric()) {
             mBiometricScrollView.addView(mBiometricView.asView());
-        } else if (Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) {
+        } else if (mPromptViewModel.getPromptKind().getValue().isCredential()) {
             addCredentialView(true /* animatePanel */, false /* animateContents */);
         } else {
             throw new IllegalStateException("Unknown configuration: "
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index ca88d40d..d6d40f2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -69,7 +69,6 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
-import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor;
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
@@ -139,7 +138,6 @@
     private Job mBiometricContextListenerJob = null;
 
     // TODO: these should be migrated out once ready
-    @NonNull private final Provider<PromptCredentialInteractor> mPromptCredentialInteractor;
     @NonNull private final Provider<PromptSelectorInteractor> mPromptSelectorInteractor;
     @NonNull private final Provider<CredentialViewModel> mCredentialViewModelProvider;
     @NonNull private final Provider<PromptViewModel> mPromptViewModelProvider;
@@ -735,7 +733,6 @@
             @NonNull LockPatternUtils lockPatternUtils,
             @NonNull Lazy<UdfpsLogger> udfpsLogger,
             @NonNull Lazy<LogContextInteractor> logContextInteractor,
-            @NonNull Provider<PromptCredentialInteractor> promptCredentialInteractorProvider,
             @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractorProvider,
             @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
             @NonNull Provider<PromptViewModel> promptViewModelProvider,
@@ -768,7 +765,6 @@
 
         mLogContextInteractor = logContextInteractor;
         mPromptSelectorInteractor = promptSelectorInteractorProvider;
-        mPromptCredentialInteractor = promptCredentialInteractorProvider;
         mPromptViewModelProvider = promptViewModelProvider;
         mCredentialViewModelProvider = credentialViewModelProvider;
 
@@ -1253,6 +1249,8 @@
         }
         mCurrentDialog = newDialog;
 
+        // TODO(b/339532378): We should check whether |allowBackgroundAuthentication| should be
+        //  removed.
         if (!promptInfo.isAllowBackgroundAuthentication() && !isOwnerInForeground()) {
             cancelIfOwnerIsNotInForeground();
         } else {
@@ -1316,8 +1314,8 @@
         config.mScaleProvider = this::getScaleFactor;
         return new AuthContainerView(config, mApplicationCoroutineScope, mFpProps, mFaceProps,
                 wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
-                mInteractionJankMonitor, mPromptCredentialInteractor, mPromptSelectorInteractor,
-                viewModel, mCredentialViewModelProvider, bgExecutor, mVibratorHelper);
+                mInteractionJankMonitor, mPromptSelectorInteractor, viewModel,
+                mCredentialViewModelProvider, bgExecutor, mVibratorHelper);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 9816896..298b87d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -32,11 +32,18 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.StatusBarState
@@ -131,6 +138,7 @@
             override fun onUnlockedChanged() {
                 updatePauseAuth()
             }
+
             override fun onLaunchTransitionFadingAwayChanged() {
                 launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway
                 updatePauseAuth()
@@ -211,7 +219,10 @@
     suspend fun listenForPrimaryBouncerToAodTransitions(scope: CoroutineScope): Job {
         return scope.launch {
             transitionInteractor
-                .transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.AOD)
+                .transition(
+                    edge = Edge.create(Scenes.Bouncer, AOD),
+                    edgeWithoutSceneContainer = Edge.create(PRIMARY_BOUNCER, AOD)
+                )
                 .collect { transitionStep ->
                     view.onDozeAmountChanged(
                         transitionStep.value,
@@ -225,8 +236,7 @@
     @VisibleForTesting
     suspend fun listenForDreamingToAodTransitions(scope: CoroutineScope): Job {
         return scope.launch {
-            transitionInteractor.transition(KeyguardState.DREAMING, KeyguardState.AOD).collect {
-                transitionStep ->
+            transitionInteractor.transition(Edge.create(DREAMING, AOD)).collect { transitionStep ->
                 view.onDozeAmountChanged(
                     transitionStep.value,
                     transitionStep.value,
@@ -239,23 +249,21 @@
     @VisibleForTesting
     suspend fun listenForAlternateBouncerToAodTransitions(scope: CoroutineScope): Job {
         return scope.launch {
-            transitionInteractor
-                .transition(KeyguardState.ALTERNATE_BOUNCER, KeyguardState.AOD)
-                .collect { transitionStep ->
-                    view.onDozeAmountChanged(
-                        transitionStep.value,
-                        transitionStep.value,
-                        UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN,
-                    )
-                }
+            transitionInteractor.transition(Edge.create(ALTERNATE_BOUNCER, AOD)).collect {
+                transitionStep ->
+                view.onDozeAmountChanged(
+                    transitionStep.value,
+                    transitionStep.value,
+                    UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN,
+                )
+            }
         }
     }
 
     @VisibleForTesting
     suspend fun listenForAodToOccludedTransitions(scope: CoroutineScope): Job {
         return scope.launch {
-            transitionInteractor.transition(KeyguardState.AOD, KeyguardState.OCCLUDED).collect {
-                transitionStep ->
+            transitionInteractor.transition(Edge.create(AOD, OCCLUDED)).collect { transitionStep ->
                 view.onDozeAmountChanged(
                     1f - transitionStep.value,
                     1f - transitionStep.value,
@@ -268,8 +276,7 @@
     @VisibleForTesting
     suspend fun listenForOccludedToAodTransition(scope: CoroutineScope): Job {
         return scope.launch {
-            transitionInteractor.transition(KeyguardState.OCCLUDED, KeyguardState.AOD).collect {
-                transitionStep ->
+            transitionInteractor.transition(Edge.create(OCCLUDED, AOD)).collect { transitionStep ->
                 view.onDozeAmountChanged(
                     transitionStep.value,
                     transitionStep.value,
@@ -282,14 +289,18 @@
     @VisibleForTesting
     suspend fun listenForGoneToAodTransition(scope: CoroutineScope): Job {
         return scope.launch {
-            transitionInteractor.transition(KeyguardState.GONE, KeyguardState.AOD).collect {
-                transitionStep ->
-                view.onDozeAmountChanged(
-                    transitionStep.value,
-                    transitionStep.value,
-                    ANIMATE_APPEAR_ON_SCREEN_OFF,
+            transitionInteractor
+                .transition(
+                    edge = Edge.create(Scenes.Gone, AOD),
+                    edgeWithoutSceneContainer = Edge.create(GONE, AOD)
                 )
-            }
+                .collect { transitionStep ->
+                    view.onDozeAmountChanged(
+                        transitionStep.value,
+                        transitionStep.value,
+                        ANIMATE_APPEAR_ON_SCREEN_OFF,
+                    )
+                }
         }
     }
 
@@ -298,13 +309,10 @@
         return scope.launch {
             transitionInteractor.dozeAmountTransition.collect { transitionStep ->
                 if (
-                    transitionStep.from == KeyguardState.AOD &&
+                    transitionStep.from == AOD &&
                         transitionStep.transitionState == TransitionState.CANCELED
                 ) {
-                    if (
-                        transitionInteractor.startedKeyguardTransitionStep.first().to !=
-                            KeyguardState.AOD
-                    ) {
+                    if (transitionInteractor.startedKeyguardTransitionStep.first().to != AOD) {
                         // If the next started transition isn't transitioning back to AOD, force
                         // doze amount to be 0f (as if the transition to the lockscreen completed).
                         view.onDozeAmountChanged(
@@ -557,6 +565,7 @@
     private fun updateScaleFactor() {
         udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) }
     }
+
     companion object {
         const val TAG = "UdfpsKeyguardViewController"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index 20e81c2..14d8caf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -16,8 +16,10 @@
 
 package com.android.systemui.biometrics.dagger
 
+import android.content.Context
 import android.content.res.Resources
 import com.android.internal.R
+import com.android.launcher3.icons.IconProvider
 import com.android.systemui.CoreStartable
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.EllipseOverlapDetectorParams
@@ -111,6 +113,9 @@
         @Provides fun providesUdfpsUtils(): UdfpsUtils = UdfpsUtils()
 
         @Provides
+        fun provideIconProvider(context: Context): IconProvider = IconProvider(context)
+
+        @Provides
         @SysUISingleton
         fun providesOverlapDetector(): OverlapDetector {
             val selectedOption =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
index cc52484..ca03a00 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
@@ -34,6 +34,7 @@
 import android.hardware.biometrics.events.AuthenticationSucceededInfo
 import android.hardware.face.FaceManager
 import android.hardware.fingerprint.FingerprintManager
+import android.util.Log
 import com.android.systemui.biometrics.shared.model.AuthenticationReason
 import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
 import com.android.systemui.biometrics.shared.model.AuthenticationState
@@ -52,6 +53,7 @@
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterIsInstance
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.shareIn
 
 /** A repository for the state of biometric authentication. */
@@ -85,6 +87,7 @@
     private val authenticationState: Flow<AuthenticationState> =
         conflatedCallbackFlow {
                 val updateAuthenticationState = { state: AuthenticationState ->
+                    Log.d(TAG, "authenticationState updated: $state")
                     trySendWithFailureLogging(state, TAG, "Error sending AuthenticationState state")
                 }
 
@@ -187,6 +190,7 @@
                         it.biometricSourceType == BiometricSourceType.FINGERPRINT)
             }
             .map { it.requestReason }
+            .onEach { Log.d(TAG, "fingerprintAuthenticationReason updated: $it") }
 
     override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
         authenticationState
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index 40d38dd..6b61adc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -30,10 +30,10 @@
 import com.android.systemui.biometrics.shared.model.toSensorStrength
 import com.android.systemui.biometrics.shared.model.toSensorType
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -42,6 +42,7 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.withContext
 
@@ -52,7 +53,7 @@
  */
 interface FingerprintPropertyRepository {
     /** Whether the fingerprint properties have been initialized yet. */
-    val propertiesInitialized: StateFlow<Boolean>
+    val propertiesInitialized: Flow<Boolean>
 
     /** The id of fingerprint sensor. */
     val sensorId: Flow<Int>
@@ -110,14 +111,8 @@
                 initialValue = UNINITIALIZED_PROPS,
             )
 
-    override val propertiesInitialized: StateFlow<Boolean> =
-        props
-            .map { it != UNINITIALIZED_PROPS }
-            .stateIn(
-                applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = props.value != UNINITIALIZED_PROPS,
-            )
+    override val propertiesInitialized: Flow<Boolean> =
+        props.map { it != UNINITIALIZED_PROPS }.onStart { emit(props.value != UNINITIALIZED_PROPS) }
 
     override val sensorId: Flow<Int> = props.map { it.sensorId }
 
@@ -141,7 +136,7 @@
 
     companion object {
         private const val TAG = "FingerprintPropertyRepositoryImpl"
-        private val UNINITIALIZED_PROPS =
+        val UNINITIALIZED_PROPS =
             FingerprintSensorPropertiesInternal(
                 -2 /* sensorId */,
                 SensorProperties.STRENGTH_CONVENIENCE,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
index 9ad3f43..58b238b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -16,12 +16,8 @@
 
 package com.android.systemui.biometrics.data.repository
 
-import android.hardware.biometrics.Flags
 import android.hardware.biometrics.PromptInfo
-import com.android.systemui.Flags.constraintBp
 import com.android.systemui.biometrics.AuthController
-import com.android.systemui.biometrics.Utils
-import com.android.systemui.biometrics.Utils.isDeviceCredentialAllowed
 import com.android.systemui.biometrics.shared.model.PromptKind
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -56,8 +52,8 @@
     /** The gatekeeper challenge, if one is associated with this prompt. */
     val challenge: StateFlow<Long?>
 
-    /** The kind of credential to use (biometric, pin, pattern, etc.). */
-    val kind: StateFlow<PromptKind>
+    /** The kind of prompt to use (biometric, pin, pattern, etc.). */
+    val promptKind: StateFlow<PromptKind>
 
     /** The package name that the prompt is called from. */
     val opPackageName: StateFlow<String?>
@@ -69,18 +65,6 @@
      */
     val isConfirmationRequired: Flow<Boolean>
 
-    /**
-     * If biometric prompt without icon needs to show for displaying content prior to credential
-     * view.
-     */
-    val showBpWithoutIconForCredential: StateFlow<Boolean>
-
-    /**
-     * Update whether biometric prompt without icon needs to show for displaying content prior to
-     * credential view, which should be set before [setPrompt].
-     */
-    fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo)
-
     /** Update the prompt configuration, which should be set before [isShowing]. */
     fun setPrompt(
         promptInfo: PromptInfo,
@@ -125,8 +109,8 @@
     private val _userId: MutableStateFlow<Int?> = MutableStateFlow(null)
     override val userId = _userId.asStateFlow()
 
-    private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.Biometric())
-    override val kind = _kind.asStateFlow()
+    private val _promptKind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.None)
+    override val promptKind = _promptKind.asStateFlow()
 
     private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null)
     override val opPackageName = _opPackageName.asStateFlow()
@@ -145,21 +129,6 @@
             }
             .distinctUntilChanged()
 
-    private val _showBpWithoutIconForCredential: MutableStateFlow<Boolean> = MutableStateFlow(false)
-    override val showBpWithoutIconForCredential = _showBpWithoutIconForCredential.asStateFlow()
-
-    override fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) {
-        val hasCredentialViewShown = kind.value !is PromptKind.Biometric
-        val showBpForCredential =
-            Flags.customBiometricPrompt() &&
-                constraintBp() &&
-                !Utils.isBiometricAllowed(promptInfo) &&
-                isDeviceCredentialAllowed(promptInfo) &&
-                promptInfo.contentView != null &&
-                !promptInfo.isContentViewMoreOptionsButtonUsed
-        _showBpWithoutIconForCredential.value = showBpForCredential && !hasCredentialViewShown
-    }
-
     override fun setPrompt(
         promptInfo: PromptInfo,
         userId: Int,
@@ -167,7 +136,7 @@
         kind: PromptKind,
         opPackageName: String,
     ) {
-        _kind.value = kind
+        _promptKind.value = kind
         _userId.value = userId
         _challenge.value = gatekeeperChallenge
         _promptInfo.value = promptInfo
@@ -178,7 +147,7 @@
         _promptInfo.value = null
         _userId.value = null
         _challenge.value = null
-        _kind.value = PromptKind.Biometric()
+        _promptKind.value = PromptKind.None
         _opPackageName.value = null
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
index 6e79e46..83aefca 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.biometrics.domain.interactor
 
 import android.app.ActivityTaskManager
+import android.util.Log
 import com.android.systemui.biometrics.data.repository.BiometricStatusRepository
 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
 import com.android.systemui.biometrics.shared.model.AuthenticationReason
@@ -26,6 +27,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.onEach
 
 /** Encapsulates business logic for interacting with biometric authentication state. */
 interface BiometricStatusInteractor {
@@ -49,15 +51,20 @@
 
     override val sfpsAuthenticationReason: Flow<AuthenticationReason> =
         combine(
-            biometricStatusRepository.fingerprintAuthenticationReason,
-            fingerprintPropertyRepository.sensorType
-        ) { reason: AuthenticationReason, sensorType ->
-            if (sensorType.isPowerButton() && reason.isReasonToAlwaysUpdateSfpsOverlay(activityTaskManager)) {
-                reason
-            } else {
-                AuthenticationReason.NotRunning
+                biometricStatusRepository.fingerprintAuthenticationReason,
+                fingerprintPropertyRepository.sensorType
+            ) { reason: AuthenticationReason, sensorType ->
+                if (
+                    sensorType.isPowerButton() &&
+                        reason.isReasonToAlwaysUpdateSfpsOverlay(activityTaskManager)
+                ) {
+                    reason
+                } else {
+                    AuthenticationReason.NotRunning
+                }
             }
-        }.distinctUntilChanged()
+            .distinctUntilChanged()
+            .onEach { Log.d(TAG, "sfpsAuthenticationReason updated: $it") }
 
     override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
         biometricStatusRepository.fingerprintAcquiredStatus
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
index 3112b67..d5b450d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
@@ -46,7 +46,7 @@
     displayStateInteractor: DisplayStateInteractor,
     udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) {
-    val propertiesInitialized: StateFlow<Boolean> = repository.propertiesInitialized
+    val propertiesInitialized: Flow<Boolean> = repository.propertiesInitialized
     val isUdfps: StateFlow<Boolean> =
         repository.sensorType
             .map { it.isUdfps() }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
index b7c0fa8..14ba8a2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
@@ -16,10 +16,8 @@
 
 package com.android.systemui.biometrics.domain.interactor
 
-import android.hardware.biometrics.PromptInfo
 import com.android.internal.widget.LockPatternView
 import com.android.internal.widget.LockscreenCredential
-import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.data.repository.PromptRepository
 import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
 import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
@@ -42,12 +40,6 @@
  * Business logic for BiometricPrompt's CredentialViews, which primarily includes checking a users
  * PIN, pattern, or password credential instead of a biometric.
  *
- * This is used to cache the calling app's options that were given to the underlying authenticate
- * APIs and should be set before any UI is shown to the user.
- *
- * There can be at most one request active at a given time. Use [resetPrompt] when no request is
- * active to clear the cache.
- *
  * Views that use any biometric should use [PromptSelectorInteractor] instead.
  */
 class PromptCredentialInteractor
@@ -74,13 +66,13 @@
                 biometricPromptRepository.promptInfo,
                 biometricPromptRepository.challenge,
                 biometricPromptRepository.userId,
-                biometricPromptRepository.kind
-            ) { promptInfo, challenge, userId, kind ->
+                biometricPromptRepository.promptKind
+            ) { promptInfo, challenge, userId, promptKind ->
                 if (promptInfo == null || userId == null || challenge == null) {
                     return@combine null
                 }
 
-                when (kind) {
+                when (promptKind) {
                     PromptKind.Pin ->
                         BiometricPromptRequest.Credential.Pin(
                             info = promptInfo,
@@ -137,28 +129,6 @@
     private val _verificationError = MutableStateFlow<CredentialStatus.Fail?>(null)
     val verificationError: Flow<CredentialStatus.Fail?> = _verificationError.asStateFlow()
 
-    /** Update the current request to use credential-based authentication instead of biometrics. */
-    fun useCredentialsForAuthentication(
-        promptInfo: PromptInfo,
-        @Utils.CredentialType kind: Int,
-        userId: Int,
-        challenge: Long,
-        opPackageName: String,
-    ) {
-        biometricPromptRepository.setPrompt(
-            promptInfo,
-            userId,
-            challenge,
-            kind.asBiometricPromptCredential(),
-            opPackageName,
-        )
-    }
-
-    /** Unset the current authentication request. */
-    fun resetPrompt() {
-        biometricPromptRepository.unsetPrompt()
-    }
-
     /**
      * Check a credential and return the attestation token (HAT) if successful.
      *
@@ -231,13 +201,3 @@
         _verificationError.value = null
     }
 }
-
-// TODO(b/251476085): remove along with Utils.CredentialType
-/** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */
-private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind =
-    when (this) {
-        Utils.CREDENTIAL_PIN -> PromptKind.Pin
-        Utils.CREDENTIAL_PASSWORD -> PromptKind.Password
-        Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern
-        else -> PromptKind.Biometric()
-    }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index 45816c1..4ba780f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.biometrics.domain.interactor
 
+import android.hardware.biometrics.Flags
 import android.hardware.biometrics.PromptInfo
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.biometrics.Utils
@@ -53,12 +54,16 @@
     /** Static metadata about the current prompt. */
     val prompt: Flow<BiometricPromptRequest.Biometric?>
 
+    /** The kind of prompt to use (biometric, pin, pattern, etc.). */
+    val promptKind: StateFlow<PromptKind>
+
     /** If using a credential is allowed. */
     val isCredentialAllowed: Flow<Boolean>
 
     /**
-     * The kind of credential the user may use as a fallback or [PromptKind.Biometric] if unknown or
-     * not [isCredentialAllowed].
+     * The kind of credential the user may use as a fallback or [PromptKind.None] if unknown or not
+     * [isCredentialAllowed]. This is separate from [promptKind], even if [promptKind] is
+     * [PromptKind.Biometric], [credentialKind] should still be one of pin/pattern/password.
      */
     val credentialKind: Flow<PromptKind>
 
@@ -71,34 +76,20 @@
     /** Fingerprint sensor type */
     val sensorType: Flow<FingerprintSensorType>
 
-    /**
-     * If biometric prompt without icon needs to show for displaying content prior to credential
-     * view.
-     */
-    val showBpWithoutIconForCredential: StateFlow<Boolean>
+    /** Switch to the credential view. */
+    fun onSwitchToCredential()
 
     /**
-     * Update whether biometric prompt without icon needs to show for displaying content prior to
-     * credential view, which should be set before [PromptRepository.setPrompt].
+     * Update the kind of prompt (biometric prompt w/ or w/o sensor icon, pin view, pattern view,
+     * etc).
      */
-    fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo)
-
-    /** Use biometrics for authentication. */
-    fun useBiometricsForAuthentication(
+    fun setPrompt(
         promptInfo: PromptInfo,
-        userId: Int,
-        challenge: Long,
+        effectiveUserId: Int,
         modalities: BiometricModalities,
-        opPackageName: String,
-    )
-
-    /** Use credential-based authentication instead of biometrics. */
-    fun useCredentialsForAuthentication(
-        promptInfo: PromptInfo,
-        @Utils.CredentialType kind: Int,
-        userId: Int,
         challenge: Long,
         opPackageName: String,
+        onSwitchToCredential: Boolean,
     )
 
     /** Unset the current authentication request. */
@@ -111,7 +102,7 @@
 constructor(
     fingerprintPropertyRepository: FingerprintPropertyRepository,
     private val promptRepository: PromptRepository,
-    lockPatternUtils: LockPatternUtils,
+    private val lockPatternUtils: LockPatternUtils,
 ) : PromptSelectorInteractor {
 
     override val prompt: Flow<BiometricPromptRequest.Biometric?> =
@@ -119,7 +110,7 @@
             promptRepository.promptInfo,
             promptRepository.challenge,
             promptRepository.userId,
-            promptRepository.kind,
+            promptRepository.promptKind,
             promptRepository.opPackageName,
         ) { promptInfo, challenge, userId, kind, opPackageName ->
             if (
@@ -141,6 +132,8 @@
             }
         }
 
+    override val promptKind: StateFlow<PromptKind> = promptRepository.promptKind
+
     override val isConfirmationRequired: Flow<Boolean> =
         promptRepository.isConfirmationRequired.distinctUntilChanged()
 
@@ -152,55 +145,61 @@
     override val credentialKind: Flow<PromptKind> =
         combine(prompt, isCredentialAllowed) { prompt, isAllowed ->
             if (prompt != null && isAllowed) {
-                when (
-                    getCredentialType(lockPatternUtils, prompt.userInfo.deviceCredentialOwnerId)
-                ) {
-                    Utils.CREDENTIAL_PIN -> PromptKind.Pin
-                    Utils.CREDENTIAL_PASSWORD -> PromptKind.Password
-                    Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern
-                    else -> PromptKind.Biometric()
-                }
+                getCredentialType(lockPatternUtils, prompt.userInfo.deviceCredentialOwnerId)
             } else {
-                PromptKind.Biometric()
+                PromptKind.None
             }
         }
 
     override val sensorType: Flow<FingerprintSensorType> = fingerprintPropertyRepository.sensorType
 
-    override val showBpWithoutIconForCredential = promptRepository.showBpWithoutIconForCredential
-
-    override fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) {
-        promptRepository.setShouldShowBpWithoutIconForCredential(promptInfo)
-    }
-
-    override fun useBiometricsForAuthentication(
-        promptInfo: PromptInfo,
-        userId: Int,
-        challenge: Long,
-        modalities: BiometricModalities,
-        opPackageName: String,
-    ) {
-        promptRepository.setPrompt(
-            promptInfo = promptInfo,
-            userId = userId,
-            gatekeeperChallenge = challenge,
-            kind = PromptKind.Biometric(modalities),
-            opPackageName = opPackageName,
+    override fun onSwitchToCredential() {
+        val modalities: BiometricModalities =
+            if (promptRepository.promptKind.value.isBiometric())
+                (promptRepository.promptKind.value as PromptKind.Biometric).activeModalities
+            else BiometricModalities()
+        setPrompt(
+            promptRepository.promptInfo.value!!,
+            promptRepository.userId.value!!,
+            modalities,
+            promptRepository.challenge.value!!,
+            promptRepository.opPackageName.value!!,
+            true /*onSwitchToCredential*/
         )
     }
 
-    override fun useCredentialsForAuthentication(
+    override fun setPrompt(
         promptInfo: PromptInfo,
-        @Utils.CredentialType kind: Int,
-        userId: Int,
+        effectiveUserId: Int,
+        modalities: BiometricModalities,
         challenge: Long,
         opPackageName: String,
+        onSwitchToCredential: Boolean,
     ) {
+        val hasCredentialViewShown = promptKind.value.isCredential()
+        val showBpForCredential =
+            Flags.customBiometricPrompt() &&
+                com.android.systemui.Flags.constraintBp() &&
+                !Utils.isBiometricAllowed(promptInfo) &&
+                isDeviceCredentialAllowed(promptInfo) &&
+                promptInfo.contentView != null &&
+                !promptInfo.isContentViewMoreOptionsButtonUsed
+        val showBpWithoutIconForCredential = showBpForCredential && !hasCredentialViewShown
+        var kind: PromptKind = PromptKind.None
+        if (onSwitchToCredential) {
+            kind = getCredentialType(lockPatternUtils, effectiveUserId)
+        } else if (Utils.isBiometricAllowed(promptInfo) || showBpWithoutIconForCredential) {
+            // TODO(b/330908557): check to show one pane or two pane
+            kind = PromptKind.Biometric(modalities)
+        } else if (isDeviceCredentialAllowed(promptInfo)) {
+            kind = getCredentialType(lockPatternUtils, effectiveUserId)
+        }
+
         promptRepository.setPrompt(
             promptInfo = promptInfo,
-            userId = userId,
+            userId = effectiveUserId,
             gatekeeperChallenge = challenge,
-            kind = kind.asBiometricPromptCredential(),
+            kind = kind,
             opPackageName = opPackageName,
         )
     }
@@ -209,13 +208,3 @@
         promptRepository.unsetPrompt()
     }
 }
-
-// TODO(b/251476085): remove along with Utils.CredentialType
-/** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */
-private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind =
-    when (this) {
-        Utils.CREDENTIAL_PIN -> PromptKind.Pin
-        Utils.CREDENTIAL_PASSWORD -> PromptKind.Password
-        Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern
-        else -> PromptKind.Biometric()
-    }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
index 6f079e2..4f96c1e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.biometrics.domain.model
 
+import android.content.ComponentName
 import android.graphics.Bitmap
 import android.hardware.biometrics.PromptContentView
 import android.hardware.biometrics.PromptInfo
@@ -43,6 +44,9 @@
         val logoBitmap: Bitmap? = info.logoBitmap
         val logoDescription: String? = info.logoDescription
         val negativeButtonText: String = info.negativeButtonText?.toString() ?: ""
+        val componentNameForConfirmDeviceCredentialActivity: ComponentName? =
+            info.componentNameForConfirmDeviceCredentialActivity
+        val allowBackgroundAuthentication = info.isAllowBackgroundAuthentication
     }
 
     /** Prompt using a credential (pin, pattern, password). */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index da56951..65c5b6b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -230,7 +230,7 @@
             }
 
             lifecycleScope.launch {
-                viewModel.showBpWithoutIconForCredential.collect { showWithoutIcon ->
+                viewModel.hideSensorIcon.collect { showWithoutIcon ->
                     if (!showWithoutIcon) {
                         PromptIconViewBinder.bind(
                             iconView,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index d1ad783..13ea3f5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -55,6 +55,7 @@
 import com.android.systemui.res.R
 import kotlin.math.abs
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
 
 /** Helper for [BiometricViewBinder] to handle resize transitions. */
@@ -169,14 +170,14 @@
             val flipConstraintSet = ConstraintSet()
 
             view.doOnLayout {
-                fun setVisibilities(size: PromptSize) {
+                fun setVisibilities(hideSensorIcon: Boolean, size: PromptSize) {
                     viewsToHideWhenSmall.forEach { it.showContentOrHide(forceHide = size.isSmall) }
                     largeConstraintSet.setVisibility(iconHolderView.id, View.GONE)
                     largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
                     largeConstraintSet.setVisibility(R.id.indicator, View.GONE)
                     largeConstraintSet.setVisibility(R.id.scrollView, View.GONE)
 
-                    if (viewModel.showBpWithoutIconForCredential.value) {
+                    if (hideSensorIcon) {
                         smallConstraintSet.setVisibility(iconHolderView.id, View.GONE)
                         smallConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
                         smallConstraintSet.setVisibility(R.id.indicator, View.GONE)
@@ -198,29 +199,32 @@
                                     iconParams.leftMargin = position.left
                                     mediumConstraintSet.clear(
                                         R.id.biometric_icon,
-                                        ConstraintSet.END
+                                        ConstraintSet.RIGHT
                                     )
                                     mediumConstraintSet.connect(
                                         R.id.biometric_icon,
-                                        ConstraintSet.START,
+                                        ConstraintSet.LEFT,
                                         ConstraintSet.PARENT_ID,
-                                        ConstraintSet.START
+                                        ConstraintSet.LEFT
                                     )
                                     mediumConstraintSet.setMargin(
                                         R.id.biometric_icon,
-                                        ConstraintSet.START,
+                                        ConstraintSet.LEFT,
                                         position.left
                                     )
-                                    smallConstraintSet.clear(R.id.biometric_icon, ConstraintSet.END)
+                                    smallConstraintSet.clear(
+                                        R.id.biometric_icon,
+                                        ConstraintSet.RIGHT
+                                    )
                                     smallConstraintSet.connect(
                                         R.id.biometric_icon,
-                                        ConstraintSet.START,
+                                        ConstraintSet.LEFT,
                                         ConstraintSet.PARENT_ID,
-                                        ConstraintSet.START
+                                        ConstraintSet.LEFT
                                     )
                                     smallConstraintSet.setMargin(
                                         R.id.biometric_icon,
-                                        ConstraintSet.START,
+                                        ConstraintSet.LEFT,
                                         position.left
                                     )
                                 }
@@ -251,32 +255,32 @@
                                     iconParams.rightMargin = position.right
                                     mediumConstraintSet.clear(
                                         R.id.biometric_icon,
-                                        ConstraintSet.START
+                                        ConstraintSet.LEFT
                                     )
                                     mediumConstraintSet.connect(
                                         R.id.biometric_icon,
-                                        ConstraintSet.END,
+                                        ConstraintSet.RIGHT,
                                         ConstraintSet.PARENT_ID,
-                                        ConstraintSet.END
+                                        ConstraintSet.RIGHT
                                     )
                                     mediumConstraintSet.setMargin(
                                         R.id.biometric_icon,
-                                        ConstraintSet.END,
+                                        ConstraintSet.RIGHT,
                                         position.right
                                     )
                                     smallConstraintSet.clear(
                                         R.id.biometric_icon,
-                                        ConstraintSet.START
+                                        ConstraintSet.LEFT
                                     )
                                     smallConstraintSet.connect(
                                         R.id.biometric_icon,
-                                        ConstraintSet.END,
+                                        ConstraintSet.RIGHT,
                                         ConstraintSet.PARENT_ID,
-                                        ConstraintSet.END
+                                        ConstraintSet.RIGHT
                                     )
                                     smallConstraintSet.setMargin(
                                         R.id.biometric_icon,
-                                        ConstraintSet.END,
+                                        ConstraintSet.RIGHT,
                                         position.right
                                     )
                                 }
@@ -362,12 +366,16 @@
                             }
                         }
                     }
+                    lifecycleScope.launch {
+                        combine(viewModel.hideSensorIcon, viewModel.size, ::Pair).collect {
+                            (hideSensorIcon, size) ->
+                            setVisibilities(hideSensorIcon, size)
+                        }
+                    }
 
                     lifecycleScope.launch {
                         combine(viewModel.position, viewModel.size, ::Pair).collect {
                             (position, size) ->
-                            setVisibilities(size)
-
                             if (position.isLeft) {
                                 if (size.isSmall) {
                                     flipConstraintSet.clone(smallConstraintSet)
@@ -378,15 +386,15 @@
                                 // Move all content to other panel
                                 flipConstraintSet.connect(
                                     R.id.scrollView,
-                                    ConstraintSet.START,
+                                    ConstraintSet.LEFT,
                                     R.id.midGuideline,
-                                    ConstraintSet.START
+                                    ConstraintSet.LEFT
                                 )
                                 flipConstraintSet.connect(
                                     R.id.scrollView,
-                                    ConstraintSet.END,
+                                    ConstraintSet.RIGHT,
                                     R.id.rightGuideline,
-                                    ConstraintSet.END
+                                    ConstraintSet.RIGHT
                                 )
                             }
 
@@ -481,7 +489,7 @@
                                 v.showContentOrHide(forceHide = size.isSmall)
                             }
 
-                            if (viewModel.showBpWithoutIconForCredential.value) {
+                            if (viewModel.hideSensorIcon.first()) {
                                 iconHolderView.visibility = View.GONE
                             }
 
@@ -492,10 +500,6 @@
                                 viewsToFadeInOnSizeChange.forEach { it.alpha = 0f }
                             }
 
-                            // TODO(b/302735104): Fix wrong height due to the delay of
-                            // PromptContentView. addOnLayoutChangeListener() will cause crash
-                            // when showing credential view, since |PromptIconViewModel| won't
-                            // release the flow.
                             // propagate size changes to legacy panel controller and animate
                             // transitions
                             view.doOnLayout {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index 4bdbfa2..ff7ac35 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.graphics.PorterDuff
 import android.graphics.PorterDuffColorFilter
+import android.util.Log
 import android.view.LayoutInflater
 import android.view.View
 import android.view.WindowManager
@@ -91,6 +92,13 @@
                                     showIndicatorForDeviceEntry,
                                     progressBarIsVisible) =
                                     combinedFlows
+                                Log.d(
+                                    TAG,
+                                    "systemServerAuthReason = $systemServerAuthReason, " +
+                                        "showIndicatorForDeviceEntry = " +
+                                        "$showIndicatorForDeviceEntry, " +
+                                        "progressBarIsVisible = $progressBarIsVisible"
+                                )
                                 if (!isInRearDisplayMode) {
                                     if (progressBarIsVisible) {
                                         hide()
@@ -114,6 +122,10 @@
     /** Show the side fingerprint sensor indicator */
     private fun show() {
         if (overlayView?.isAttachedToWindow == true) {
+            Log.d(
+                TAG,
+                "show(): overlayView $overlayView isAttachedToWindow already, ignoring show request"
+            )
             return
         }
 
@@ -128,6 +140,7 @@
             )
         bind(overlayView!!, overlayViewModel, fpsUnlockTracker.get(), windowManager.get())
         overlayView!!.visibility = View.INVISIBLE
+        Log.d(TAG, "show(): adding overlayView $overlayView")
         windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams)
     }
 
@@ -137,6 +150,7 @@
             val lottie = overlayView!!.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
             lottie.pauseAnimation()
             lottie.removeAllLottieOnCompositionLoadedListener()
+            Log.d(TAG, "hide(): removing overlayView $overlayView, setting to null")
             windowManager.get().removeView(overlayView)
             overlayView = null
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 2104f3e..156ec6b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -16,7 +16,10 @@
 
 package com.android.systemui.biometrics.ui.viewmodel
 
+import android.app.ActivityTaskManager
+import android.content.ComponentName
 import android.content.Context
+import android.content.pm.ActivityInfo
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.graphics.Rect
@@ -26,18 +29,22 @@
 import android.hardware.biometrics.BiometricPrompt
 import android.hardware.biometrics.Flags.customBiometricPrompt
 import android.hardware.biometrics.PromptContentView
+import android.os.UserHandle
 import android.util.Log
 import android.util.RotationUtils
 import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
+import com.android.launcher3.icons.IconProvider
 import com.android.systemui.Flags.bpTalkback
 import com.android.systemui.Flags.constraintBp
 import com.android.systemui.biometrics.UdfpsUtils
 import com.android.systemui.biometrics.Utils
+import com.android.systemui.biometrics.Utils.isSystem
 import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
 import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.android.systemui.biometrics.shared.model.DisplayRotation
@@ -67,11 +74,13 @@
 @Inject
 constructor(
     displayStateInteractor: DisplayStateInteractor,
-    promptSelectorInteractor: PromptSelectorInteractor,
+    private val promptSelectorInteractor: PromptSelectorInteractor,
     @Application private val context: Context,
     private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
     private val biometricStatusInteractor: BiometricStatusInteractor,
-    private val udfpsUtils: UdfpsUtils
+    private val udfpsUtils: UdfpsUtils,
+    private val iconProvider: IconProvider,
+    private val activityTaskManager: ActivityTaskManager,
 ) {
     /** The set of modalities available for this prompt */
     val modalities: Flow<BiometricModalities> =
@@ -195,8 +204,11 @@
     /** The kind of credential the user has. */
     val credentialKind: Flow<PromptKind> = promptSelectorInteractor.credentialKind
 
-    val showBpWithoutIconForCredential: StateFlow<Boolean> =
-        promptSelectorInteractor.showBpWithoutIconForCredential
+    /** The kind of prompt to use (biometric, pin, pattern, etc.). */
+    val promptKind: StateFlow<PromptKind> = promptSelectorInteractor.promptKind
+
+    /** Whether the sensor icon on biometric prompt ui should be hidden. */
+    val hideSensorIcon: Flow<Boolean> = modalities.map { it.isEmpty }.distinctUntilChanged()
 
     /** The label to use for the cancel button. */
     val negativeButtonText: Flow<String> =
@@ -496,14 +508,7 @@
                     !(customBiometricPrompt() && constraintBp()) || it == null -> null
                     it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme)
                     it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
-                    else ->
-                        try {
-                            val info = context.getApplicationInfo(it.opPackageName)
-                            context.packageManager.getApplicationIcon(info)
-                        } catch (e: Exception) {
-                            Log.w(TAG, "Cannot find icon for package " + it.opPackageName, e)
-                            null
-                        }
+                    else -> context.getUserBadgedIcon(it, iconProvider, activityTaskManager)
                 }
             }
             .distinctUntilChanged()
@@ -514,15 +519,8 @@
             .map {
                 when {
                     !(customBiometricPrompt() && constraintBp()) || it == null -> ""
-                    it.logoDescription != null -> it.logoDescription
-                    else ->
-                        try {
-                            val info = context.getApplicationInfo(it.opPackageName)
-                            context.packageManager.getApplicationLabel(info).toString()
-                        } catch (e: Exception) {
-                            Log.w(TAG, "Cannot find name for package " + it.opPackageName, e)
-                            ""
-                        }
+                    !it.logoDescription.isNullOrEmpty() -> it.logoDescription
+                    else -> context.getUserBadgedLabel(it, activityTaskManager)
                 }
             }
             .distinctUntilChanged()
@@ -896,6 +894,7 @@
      */
     fun onSwitchToCredential() {
         _forceLargeSize.value = true
+        promptSelectorInteractor.onSwitchToCredential()
     }
 
     private fun vibrateOnSuccess() {
@@ -922,15 +921,109 @@
     }
 
     companion object {
-        private const val TAG = "PromptViewModel"
+        const val TAG = "PromptViewModel"
     }
 }
 
-private fun Context.getApplicationInfo(packageName: String): ApplicationInfo =
-    packageManager.getApplicationInfo(
-        packageName,
-        PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER
-    )
+private fun Context.getUserBadgedIcon(
+    prompt: BiometricPromptRequest.Biometric,
+    iconProvider: IconProvider,
+    activityTaskManager: ActivityTaskManager
+): Drawable? {
+    var icon: Drawable? = null
+    val componentName = prompt.getComponentNameForLogo(activityTaskManager)
+    if (componentName != null && shouldShowLogoWithOverrides(componentName)) {
+        val activityInfo = getActivityInfo(componentName)
+        icon = if (activityInfo == null) null else iconProvider.getIcon(activityInfo)
+    }
+    if (icon == null) {
+        val appInfo = prompt.getApplicationInfoForLogo(this, componentName)
+        if (appInfo == null) {
+            Log.w(PromptViewModel.TAG, "Cannot find app logo for package $opPackageName")
+            return null
+        } else {
+            icon = packageManager.getApplicationIcon(appInfo)
+        }
+    }
+    return packageManager.getUserBadgedIcon(icon, UserHandle.of(prompt.userInfo.userId))
+}
+
+private fun Context.getUserBadgedLabel(
+    prompt: BiometricPromptRequest.Biometric,
+    activityTaskManager: ActivityTaskManager
+): String {
+    val componentName = prompt.getComponentNameForLogo(activityTaskManager)
+    val appInfo = prompt.getApplicationInfoForLogo(this, componentName)
+    return if (appInfo == null || packageManager.getApplicationLabel(appInfo).isNullOrEmpty()) {
+        Log.w(PromptViewModel.TAG, "Cannot find app logo for package $opPackageName")
+        ""
+    } else {
+        packageManager
+            .getUserBadgedLabel(packageManager.getApplicationLabel(appInfo), UserHandle.of(userId))
+            .toString()
+    }
+}
+
+private fun BiometricPromptRequest.Biometric.getComponentNameForLogo(
+    activityTaskManager: ActivityTaskManager
+): ComponentName? {
+    val topActivity: ComponentName? = activityTaskManager.getTasks(1).firstOrNull()?.topActivity
+    return when {
+        componentNameForConfirmDeviceCredentialActivity != null ->
+            componentNameForConfirmDeviceCredentialActivity
+        topActivity?.packageName.contentEquals(opPackageName) -> topActivity
+        else -> {
+            Log.w(PromptViewModel.TAG, "Top activity $topActivity is not the client $opPackageName")
+            null
+        }
+    }
+}
+
+private fun BiometricPromptRequest.Biometric.getApplicationInfoForLogo(
+    context: Context,
+    componentNameForLogo: ComponentName?
+): ApplicationInfo? {
+    val packageName =
+        when {
+            componentNameForLogo != null -> componentNameForLogo.packageName
+            // TODO(b/339532378): We should check whether |allowBackgroundAuthentication| should be
+            // removed.
+            // This is being consistent with the check in [AuthController.showDialog()].
+            allowBackgroundAuthentication || isSystem(context, opPackageName) -> opPackageName
+            else -> null
+        }
+    return if (packageName == null) {
+        Log.w(PromptViewModel.TAG, "Cannot find application info for $opPackageName")
+        null
+    } else {
+        context.getApplicationInfo(packageName)
+    }
+}
+
+private fun Context.shouldShowLogoWithOverrides(componentName: ComponentName): Boolean {
+    return resources
+        .getStringArray(R.array.biometric_dialog_package_names_for_logo_with_overrides)
+        .find { componentName.packageName.contentEquals(it) } != null
+}
+
+private fun Context.getActivityInfo(componentName: ComponentName): ActivityInfo? =
+    try {
+        packageManager.getActivityInfo(componentName, 0)
+    } catch (e: PackageManager.NameNotFoundException) {
+        Log.w(PromptViewModel.TAG, "Cannot find activity info for $opPackageName", e)
+        null
+    }
+
+private fun Context.getApplicationInfo(packageName: String): ApplicationInfo? =
+    try {
+        packageManager.getApplicationInfo(
+            packageName,
+            PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER
+        )
+    } catch (e: PackageManager.NameNotFoundException) {
+        Log.w(PromptViewModel.TAG, "Cannot find application info for $opPackageName", e)
+        null
+    }
 
 /** How the fingerprint sensor was started for the prompt. */
 enum class FingerprintStartMode {
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index dd8c0df..911145b 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -204,11 +204,7 @@
         isEnabled: Boolean,
         @StringRes infoResId: Int
     ) {
-        getAutoOnToggle(dialog).apply {
-            isChecked = isEnabled
-            setEnabled(true)
-            alpha = ENABLED_ALPHA
-        }
+        getAutoOnToggle(dialog).isChecked = isEnabled
         getAutoOnToggleInfoTextView(dialog).text = dialog.context.getString(infoResId)
     }
 
@@ -236,12 +232,8 @@
         }
 
         getAutoOnToggleView(dialog).visibility = initialUiProperties.autoOnToggleVisibility
-        getAutoOnToggle(dialog).setOnCheckedChangeListener { view, isChecked ->
+        getAutoOnToggle(dialog).setOnCheckedChangeListener { _, isChecked ->
             mutableBluetoothAutoOnToggle.value = isChecked
-            view.apply {
-                isEnabled = false
-                alpha = DISABLED_ALPHA
-            }
             uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_AUTO_ON_TOGGLE_CLICKED)
         }
     }
@@ -427,8 +419,7 @@
         const val ACTION_PREVIOUSLY_CONNECTED_DEVICE =
             "com.android.settings.PREVIOUSLY_CONNECTED_DEVICE"
         const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS"
-        const val ACTION_AUDIO_SHARING =
-            "com.google.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS"
+        const val ACTION_AUDIO_SHARING = "com.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS"
         const val DISABLED_ALPHA = 0.3f
         const val ENABLED_ALPHA = 1f
         const val PROGRESS_BAR_ANIMATION_DURATION_MS = 1500L
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogModule.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogModule.kt
index bf22563..2e9169e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogModule.kt
@@ -13,13 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.systemui.bluetooth.qsdialog
 
-package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
-
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
+@Module
+interface BluetoothTileDialogModule {
+    @Binds
+    @SysUISingleton
+    fun bindDeviceItemActionInteractor(
+        impl: DeviceItemActionInteractorImpl
+    ): DeviceItemActionInteractor
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index 4369f3f..94f465d 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -62,6 +62,7 @@
 @Inject
 constructor(
     private val deviceItemInteractor: DeviceItemInteractor,
+    private val deviceItemActionInteractor: DeviceItemActionInteractor,
     private val bluetoothStateInteractor: BluetoothStateInteractor,
     private val bluetoothAutoOnInteractor: BluetoothAutoOnInteractor,
     private val audioSharingInteractor: AudioSharingInteractor,
@@ -192,7 +193,7 @@
 
                 // deviceItemClick is emitted when user clicked on a device item.
                 dialogDelegate.deviceItemClick
-                    .onEach { deviceItemInteractor.updateDeviceItemOnClick(it) }
+                    .onEach { deviceItemActionInteractor.onClick(it, dialog) }
                     .launchIn(this)
 
                 // contentHeight is emitted when the dialog is dismissed.
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
new file mode 100644
index 0000000..9311760
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.bluetooth.qsdialog
+
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+/** Defines interface for click handling of a DeviceItem. */
+interface DeviceItemActionInteractor {
+    suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog)
+}
+
+@SysUISingleton
+open class DeviceItemActionInteractorImpl
+@Inject
+constructor(
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val logger: BluetoothTileDialogLogger,
+    private val uiEventLogger: UiEventLogger,
+) : DeviceItemActionInteractor {
+
+    override suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog) {
+        withContext(backgroundDispatcher) {
+            logger.logDeviceClick(deviceItem.cachedBluetoothDevice.address, deviceItem.type)
+
+            deviceItem.cachedBluetoothDevice.apply {
+                when (deviceItem.type) {
+                    DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE -> {
+                        disconnect()
+                        uiEventLogger.log(BluetoothTileDialogUiEvent.ACTIVE_DEVICE_DISCONNECT)
+                    }
+                    DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
+                        uiEventLogger.log(BluetoothTileDialogUiEvent.AUDIO_SHARING_DEVICE_CLICKED)
+                    }
+                    DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
+                        setActive()
+                        uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE)
+                    }
+                    DeviceItemType.CONNECTED_BLUETOOTH_DEVICE -> {
+                        disconnect()
+                        uiEventLogger.log(
+                            BluetoothTileDialogUiEvent.CONNECTED_OTHER_DEVICE_DISCONNECT
+                        )
+                    }
+                    DeviceItemType.SAVED_BLUETOOTH_DEVICE -> {
+                        connect()
+                        uiEventLogger.log(BluetoothTileDialogUiEvent.SAVED_DEVICE_CONNECT)
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
index 66e593b..1526cd9 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
@@ -20,7 +20,6 @@
 import android.bluetooth.BluetoothDevice
 import android.content.Context
 import android.media.AudioManager
-import com.android.internal.logging.UiEventLogger
 import com.android.settingslib.bluetooth.BluetoothCallback
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.bluetooth.LocalBluetoothManager
@@ -52,7 +51,6 @@
     private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter(),
     private val localBluetoothManager: LocalBluetoothManager?,
     private val systemClock: SystemClock,
-    private val uiEventLogger: UiEventLogger,
     private val logger: BluetoothTileDialogLogger,
     @Application private val coroutineScope: CoroutineScope,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
@@ -169,38 +167,6 @@
         )
     }
 
-    internal suspend fun updateDeviceItemOnClick(deviceItem: DeviceItem) {
-        withContext(backgroundDispatcher) {
-            logger.logDeviceClick(deviceItem.cachedBluetoothDevice.address, deviceItem.type)
-
-            deviceItem.cachedBluetoothDevice.apply {
-                when (deviceItem.type) {
-                    DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE -> {
-                        disconnect()
-                        uiEventLogger.log(BluetoothTileDialogUiEvent.ACTIVE_DEVICE_DISCONNECT)
-                    }
-                    DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
-                        uiEventLogger.log(BluetoothTileDialogUiEvent.AUDIO_SHARING_DEVICE_CLICKED)
-                    }
-                    DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
-                        setActive()
-                        uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE)
-                    }
-                    DeviceItemType.CONNECTED_BLUETOOTH_DEVICE -> {
-                        disconnect()
-                        uiEventLogger.log(
-                            BluetoothTileDialogUiEvent.CONNECTED_OTHER_DEVICE_DISCONNECT
-                        )
-                    }
-                    DeviceItemType.SAVED_BLUETOOTH_DEVICE -> {
-                        connect()
-                        uiEventLogger.log(BluetoothTileDialogUiEvent.SAVED_DEVICE_CONNECT)
-                    }
-                }
-            }
-        }
-    }
-
     internal fun setDeviceItemFactoryListForTesting(list: List<DeviceItemFactory>) {
         deviceItemFactoryList = list
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index fa19bf4..e0334a0 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -29,7 +29,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.kotlin.BooleanFlowOperators.or
+import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
 import com.android.systemui.util.time.SystemClock
 import dagger.Lazy
 import javax.inject.Inject
@@ -78,7 +78,7 @@
             bouncerRepository.alternateBouncerUIAvailable
         }
     private val isDozingOrAod: Flow<Boolean> =
-        or(
+        anyOf(
                 keyguardTransitionInteractor.get().transitionValue(KeyguardState.DOZING).map {
                     it > 0f
                 },
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt
index c018ecb..0544a4f 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt
@@ -18,6 +18,8 @@
 
 import android.content.Context
 import android.os.UserManager
+import com.android.settingslib.RestrictedLockUtils
+import com.android.systemui.Flags.enforceBrightnessBaseUserRestriction
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -66,7 +68,18 @@
                         user.id
                     )
                     ?.let { PolicyRestriction.Restricted(it) }
-                    ?: PolicyRestriction.NoRestriction
+                    ?: if (
+                        enforceBrightnessBaseUserRestriction() &&
+                            userRestrictionChecker.hasBaseUserRestriction(
+                                applicationContext,
+                                UserManager.DISALLOW_CONFIG_BRIGHTNESS,
+                                user.id
+                            )
+                    ) {
+                        PolicyRestriction.Restricted(RestrictedLockUtils.EnforcedAdmin())
+                    } else {
+                        PolicyRestriction.NoRestriction
+                    }
             }
             .flowOn(backgroundDispatcher)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
index c1be37a..a51d8ff 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
@@ -23,7 +23,6 @@
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableIntStateOf
 import androidx.compose.runtime.remember
@@ -32,6 +31,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.PlatformSlider
 import com.android.systemui.brightness.shared.GammaBrightness
 import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
@@ -107,10 +107,13 @@
     viewModel: BrightnessSliderViewModel,
     modifier: Modifier = Modifier,
 ) {
-    val gamma: Int by viewModel.currentBrightness.map { it.value }.collectAsState(initial = 0)
+    val gamma: Int by
+        viewModel.currentBrightness.map { it.value }.collectAsStateWithLifecycle(initialValue = 0)
     val coroutineScope = rememberCoroutineScope()
     val restriction by
-        viewModel.policyRestriction.collectAsState(initial = PolicyRestriction.NoRestriction)
+        viewModel.policyRestriction.collectAsStateWithLifecycle(
+            initialValue = PolicyRestriction.NoRestriction
+        )
 
     BrightnessSlider(
         gammaValue = gamma,
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index beaa170..2eca02c 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -31,9 +31,12 @@
 import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -88,6 +91,8 @@
     private final JavaAdapter mJavaAdapter;
     private final SystemClock mSystemClock;
     private final Lazy<SelectedUserInteractor> mUserInteractor;
+    private final Lazy<DeviceEntryInteractor> mDeviceEntryInteractor;
+    private final Lazy<SceneContainerOcclusionInteractor> mSceneContainerOcclusionInteractor;
 
     private int mState;
     private boolean mShowingAod;
@@ -170,7 +175,9 @@
             JavaAdapter javaAdapter,
             SystemClock systemClock,
             Lazy<SelectedUserInteractor> userInteractor,
-            Lazy<CommunalInteractor> communalInteractorLazy) {
+            Lazy<CommunalInteractor> communalInteractorLazy,
+            Lazy<DeviceEntryInteractor> deviceEntryInteractor,
+            Lazy<SceneContainerOcclusionInteractor> sceneContainerOcclusionInteractor) {
         mFalsingDataProvider = falsingDataProvider;
         mFalsingManager = falsingManager;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -186,6 +193,8 @@
         mSystemClock = systemClock;
         mUserInteractor = userInteractor;
         mCommunalInteractorLazy = communalInteractorLazy;
+        mDeviceEntryInteractor = deviceEntryInteractor;
+        mSceneContainerOcclusionInteractor = sceneContainerOcclusionInteractor;
     }
 
     @Override
@@ -196,7 +205,18 @@
         mStatusBarStateController.addCallback(mStatusBarStateListener);
         mState = mStatusBarStateController.getState();
 
-        mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
+        if (SceneContainerFlag.isEnabled()) {
+            mJavaAdapter.alwaysCollectFlow(
+                    mDeviceEntryInteractor.get().isDeviceEntered(),
+                    this::isDeviceEnteredChanged
+            );
+            mJavaAdapter.alwaysCollectFlow(
+                    mSceneContainerOcclusionInteractor.get().getInvisibleDueToOcclusion(),
+                    this::isInvisibleDueToOcclusionChanged
+            );
+        } else {
+            mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
+        }
 
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
 
@@ -206,7 +226,7 @@
         );
         final CommunalInteractor communalInteractor = mCommunalInteractorLazy.get();
         mJavaAdapter.alwaysCollectFlow(
-                BooleanFlowOperators.INSTANCE.and(
+                BooleanFlowOperators.INSTANCE.allOf(
                         communalInteractor.isCommunalEnabled(),
                         communalInteractor.isCommunalShowing()),
                 this::onShowingCommunalHubChanged
@@ -216,6 +236,14 @@
         mDockManager.addListener(mDockEventListener);
     }
 
+    public void isDeviceEnteredChanged(boolean unused) {
+        updateSensorRegistration();
+    }
+
+    public void isInvisibleDueToOcclusionChanged(boolean unused) {
+        updateSensorRegistration();
+    }
+
     @Override
     public void onSuccessfulUnlock() {
         logDebug("REAL: onSuccessfulUnlock");
@@ -292,6 +320,7 @@
 
     @Override
     public void onKeyEvent(KeyEvent ev) {
+        logDebug("REAL: onKeyEvent(" + KeyEvent.actionToString(ev.getAction()) + ")");
         // Only collect if it is an ACTION_UP action and is allow-listed
         if (ev.getAction() == KeyEvent.ACTION_UP && mAcceptedKeycodes.contains(ev.getKeyCode())) {
             mFalsingDataProvider.onKeyEvent(ev);
@@ -300,8 +329,8 @@
 
     @Override
     public void onTouchEvent(MotionEvent ev) {
-        logDebug("REAL: onTouchEvent(" + ev.getActionMasked() + ")");
-        if (!mKeyguardStateController.isShowing()) {
+        logDebug("REAL: onTouchEvent(" + MotionEvent.actionToString(ev.getActionMasked()) + ")");
+        if (!isKeyguardShowing()) {
             avoidGesture();
             return;
         }
@@ -398,12 +427,13 @@
     }
 
     private boolean shouldBeRegisteredToSensors() {
-        return mScreenOn
-                && (mState == StatusBarState.KEYGUARD
-                || (mState == StatusBarState.SHADE
-                && mKeyguardStateController.isOccluded()
-                && mKeyguardStateController.isShowing()))
-                && !mShowingAod;
+        final boolean isKeyguard = mState == StatusBarState.KEYGUARD;
+
+        final boolean isShadeOverOccludedKeyguard = mState == StatusBarState.SHADE
+                && isKeyguardShowing()
+                && isKeyguardOccluded();
+
+        return mScreenOn && !mShowingAod && (isKeyguard || isShadeOverOccludedKeyguard);
     }
 
     private void updateSensorRegistration() {
@@ -445,6 +475,32 @@
         mFalsingManager.onProximityEvent(new ProximityEventImpl(proximityEvent));
     }
 
+    /**
+     * Returns {@code true} if the keyguard is showing (whether or not the screen is on, whether or
+     * not an activity is occluding the keyguard, and whether or not the shade is open on top of the
+     * keyguard), or {@code false} if the user has dismissed the keyguard by authenticating or
+     * swiping up.
+     */
+    private boolean isKeyguardShowing() {
+        if (SceneContainerFlag.isEnabled()) {
+            return !mDeviceEntryInteractor.get().isDeviceEntered().getValue();
+        } else {
+            return mKeyguardStateController.isShowing();
+        }
+    }
+
+    /**
+     * Returns {@code true} if there is an activity display on top of ("occluding") the keyguard, or
+     * {@code false} if an activity is not occluding the keyguard (including if the keyguard is not
+     * showing at all).
+     */
+    private boolean isKeyguardOccluded() {
+        if (SceneContainerFlag.isEnabled()) {
+            return mSceneContainerOcclusionInteractor.get().getInvisibleDueToOcclusion().getValue();
+        } else {
+            return mKeyguardStateController.isOccluded();
+        }
+    }
 
     static void logDebug(String msg) {
         if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
index b289fa4..6b22137 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
@@ -61,11 +61,11 @@
     }
 
     override fun onKeyEvent(ev: KeyEvent) {
-        logDebug("NOOP: onKeyEvent(${ev.action}")
+        logDebug("NOOP: onKeyEvent(${KeyEvent.actionToString(ev.action)}")
     }
 
     override fun onTouchEvent(ev: MotionEvent) {
-        logDebug("NOOP: onTouchEvent(${ev.actionMasked})")
+        logDebug("NOOP: onTouchEvent(${MotionEvent.actionToString(ev.actionMasked)})")
     }
 
     override fun onMotionEventComplete() {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
index b269967..ba236ba 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -18,6 +18,8 @@
 
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
+import static com.android.systemui.Flags.screenshotShelfUi2;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -25,6 +27,7 @@
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.annotation.Nullable;
+import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.content.Context;
 import android.content.res.Resources;
@@ -36,6 +39,7 @@
 import android.graphics.drawable.Icon;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
+import android.util.Log;
 import android.util.MathUtils;
 import android.util.TypedValue;
 import android.view.DisplayCutout;
@@ -58,6 +62,12 @@
 import com.android.systemui.screenshot.DraggableConstraintLayout;
 import com.android.systemui.screenshot.FloatingWindowUtil;
 import com.android.systemui.screenshot.OverlayActionChip;
+import com.android.systemui.screenshot.ui.binder.ActionButtonViewBinder;
+import com.android.systemui.screenshot.ui.viewmodel.ActionButtonAppearance;
+import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel;
+
+import kotlin.Unit;
+import kotlin.jvm.functions.Function0;
 
 import java.util.ArrayList;
 
@@ -85,7 +95,7 @@
 
     private final DisplayMetrics mDisplayMetrics;
     private final AccessibilityManager mAccessibilityManager;
-    private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>();
+    private final ArrayList<View> mActionChips = new ArrayList<>();
 
     private View mClipboardPreview;
     private ImageView mImagePreview;
@@ -93,11 +103,13 @@
     private TextView mHiddenPreview;
     private LinearLayout mMinimizedPreview;
     private View mPreviewBorder;
-    private OverlayActionChip mShareChip;
-    private OverlayActionChip mRemoteCopyChip;
+    private View mShareChip;
+    private View mRemoteCopyChip;
     private View mActionContainerBackground;
     private View mDismissButton;
     private LinearLayout mActionContainer;
+    private ClipboardOverlayCallbacks mClipboardCallbacks;
+    private ActionButtonViewBinder mActionButtonViewBinder = new ActionButtonViewBinder();
 
     public ClipboardOverlayView(Context context) {
         this(context, null);
@@ -128,17 +140,7 @@
         mRemoteCopyChip = requireViewById(R.id.remote_copy_chip);
         mDismissButton = requireViewById(R.id.dismiss_button);
 
-        mShareChip.setAlpha(1);
-        mRemoteCopyChip.setAlpha(1);
-        mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share));
-
-        mRemoteCopyChip.setIcon(
-                Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
-        mShareChip.setIcon(
-                Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
-
-        mRemoteCopyChip.setContentDescription(
-                mContext.getString(R.string.clipboard_send_nearby_description));
+        bindDefaultActionChips();
 
         mTextPreview.getViewTreeObserver().addOnPreDrawListener(() -> {
             int availableHeight = mTextPreview.getHeight()
@@ -149,15 +151,71 @@
         super.onFinishInflate();
     }
 
+    private void bindDefaultActionChips() {
+        if (screenshotShelfUi2()) {
+            mActionButtonViewBinder.bind(mRemoteCopyChip,
+                    ActionButtonViewModel.Companion.withNextId(
+                            new ActionButtonAppearance(
+                                    Icon.createWithResource(mContext,
+                                            R.drawable.ic_baseline_devices_24).loadDrawable(
+                                            mContext),
+                                    null,
+                                    mContext.getString(R.string.clipboard_send_nearby_description),
+                                    true),
+                            new Function0<>() {
+                                @Override
+                                public Unit invoke() {
+                                    if (mClipboardCallbacks != null) {
+                                        mClipboardCallbacks.onRemoteCopyButtonTapped();
+                                    }
+                                    return null;
+                                }
+                            }));
+            mActionButtonViewBinder.bind(mShareChip,
+                    ActionButtonViewModel.Companion.withNextId(
+                            new ActionButtonAppearance(
+                                    Icon.createWithResource(mContext,
+                                            R.drawable.ic_screenshot_share).loadDrawable(mContext),
+                                    null,
+                                    mContext.getString(com.android.internal.R.string.share),
+                                    true),
+                            new Function0<>() {
+                                @Override
+                                public Unit invoke() {
+                                    if (mClipboardCallbacks != null) {
+                                        mClipboardCallbacks.onShareButtonTapped();
+                                    }
+                                    return null;
+                                }
+                            }));
+        } else {
+            mShareChip.setAlpha(1);
+            mRemoteCopyChip.setAlpha(1);
+
+            ((ImageView) mRemoteCopyChip.findViewById(R.id.overlay_action_chip_icon)).setImageIcon(
+                    Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24));
+            ((ImageView) mShareChip.findViewById(R.id.overlay_action_chip_icon)).setImageIcon(
+                    Icon.createWithResource(mContext, R.drawable.ic_screenshot_share));
+
+            mShareChip.setContentDescription(
+                    mContext.getString(com.android.internal.R.string.share));
+            mRemoteCopyChip.setContentDescription(
+                    mContext.getString(R.string.clipboard_send_nearby_description));
+        }
+    }
+
     @Override
     public void setCallbacks(SwipeDismissCallbacks callbacks) {
         super.setCallbacks(callbacks);
         ClipboardOverlayCallbacks clipboardCallbacks = (ClipboardOverlayCallbacks) callbacks;
-        mShareChip.setOnClickListener(v -> clipboardCallbacks.onShareButtonTapped());
+        if (!screenshotShelfUi2()) {
+            mShareChip.setOnClickListener(v -> clipboardCallbacks.onShareButtonTapped());
+            mRemoteCopyChip.setOnClickListener(v -> clipboardCallbacks.onRemoteCopyButtonTapped());
+        }
         mDismissButton.setOnClickListener(v -> clipboardCallbacks.onDismissButtonTapped());
-        mRemoteCopyChip.setOnClickListener(v -> clipboardCallbacks.onRemoteCopyButtonTapped());
         mClipboardPreview.setOnClickListener(v -> clipboardCallbacks.onPreviewTapped());
         mMinimizedPreview.setOnClickListener(v -> clipboardCallbacks.onMinimizedViewTapped());
+        mClipboardCallbacks = clipboardCallbacks;
     }
 
     void setEditAccessibilityAction(boolean editable) {
@@ -285,7 +343,7 @@
     }
 
     void resetActionChips() {
-        for (OverlayActionChip chip : mActionChips) {
+        for (View chip : mActionChips) {
             mActionContainer.removeView(chip);
         }
         mActionChips.clear();
@@ -437,7 +495,12 @@
 
     void setActionChip(RemoteAction action, Runnable onFinish) {
         mActionContainerBackground.setVisibility(View.VISIBLE);
-        OverlayActionChip chip = constructActionChip(action, onFinish);
+        View chip;
+        if (screenshotShelfUi2()) {
+            chip = constructShelfActionChip(action, onFinish);
+        } else {
+            chip = constructActionChip(action, onFinish);
+        }
         mActionContainer.addView(chip);
         mActionChips.add(chip);
     }
@@ -450,6 +513,27 @@
         v.setVisibility(View.VISIBLE);
     }
 
+    private View constructShelfActionChip(RemoteAction action, Runnable onFinish) {
+        View chip = LayoutInflater.from(mContext).inflate(
+                R.layout.shelf_action_chip, mActionContainer, false);
+        mActionButtonViewBinder.bind(chip, ActionButtonViewModel.Companion.withNextId(
+                new ActionButtonAppearance(action.getIcon().loadDrawable(mContext),
+                        action.getTitle(), action.getTitle(), false), new Function0<>() {
+                    @Override
+                    public Unit invoke() {
+                        try {
+                            action.getActionIntent().send();
+                            onFinish.run();
+                        } catch (PendingIntent.CanceledException e) {
+                            Log.e(TAG, "Failed to send intent");
+                        }
+                        return null;
+                    }
+                }));
+
+        return chip;
+    }
+
     private OverlayActionChip constructActionChip(RemoteAction action, Runnable onFinish) {
         OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate(
                 R.layout.overlay_action_chip, mActionContainer, false);
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
index ff9fba4..740a93e 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
@@ -18,6 +18,8 @@
 
 import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
 
+import static com.android.systemui.Flags.screenshotShelfUi2;
+
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 import android.content.Context;
@@ -57,8 +59,13 @@
      */
     @Provides
     static ClipboardOverlayView provideClipboardOverlayView(@OverlayWindowContext Context context) {
-        return (ClipboardOverlayView) LayoutInflater.from(context).inflate(
-                R.layout.clipboard_overlay, null);
+        if (screenshotShelfUi2()) {
+            return (ClipboardOverlayView) LayoutInflater.from(context).inflate(
+                    R.layout.clipboard_overlay2, null);
+        } else {
+            return (ClipboardOverlayView) LayoutInflater.from(context).inflate(
+                    R.layout.clipboard_overlay, null);
+        }
     }
 
     @Qualifier
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt
index 5c64dc6..1c16429 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt
@@ -18,6 +18,7 @@
 
 import android.os.UserHandle
 import com.android.systemui.common.shared.model.PackageChangeModel
+import com.android.systemui.common.shared.model.PackageInstallSession
 import kotlinx.coroutines.flow.Flow
 
 interface PackageChangeRepository {
@@ -28,4 +29,7 @@
      * [UserHandle.USER_ALL] may be used to listen to all users.
      */
     fun packageChanged(user: UserHandle): Flow<PackageChangeModel>
+
+    /** Emits a list of all known install sessions associated with the primary user. */
+    val packageInstallSessionsForPrimaryUser: Flow<List<PackageInstallSession>>
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt
index 712a352..41b03f1 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt
@@ -18,6 +18,7 @@
 
 import android.os.UserHandle
 import com.android.systemui.common.shared.model.PackageChangeModel
+import com.android.systemui.common.shared.model.PackageInstallSession
 import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -27,6 +28,7 @@
 class PackageChangeRepositoryImpl
 @Inject
 constructor(
+    packageInstallerMonitor: PackageInstallerMonitor,
     private val monitorFactory: PackageUpdateMonitor.Factory,
 ) : PackageChangeRepository {
     /**
@@ -37,4 +39,7 @@
 
     override fun packageChanged(user: UserHandle): Flow<PackageChangeModel> =
         monitor.packageChanged.filter { user == UserHandle.ALL || user == it.user }
+
+    override val packageInstallSessionsForPrimaryUser: Flow<List<PackageInstallSession>> =
+        packageInstallerMonitor.installSessionsForPrimaryUser
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt
new file mode 100644
index 0000000..46db346
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt
@@ -0,0 +1,154 @@
+/*
+ * 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.common.data.repository
+
+import android.content.pm.PackageInstaller
+import android.os.Handler
+import com.android.internal.annotations.GuardedBy
+import com.android.systemui.common.shared.model.PackageInstallSession
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.PackageChangeRepoLog
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.dropWhile
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+
+/** Monitors package install sessions for all users. */
+@SysUISingleton
+class PackageInstallerMonitor
+@Inject
+constructor(
+    @Background private val bgHandler: Handler,
+    @Background private val bgScope: CoroutineScope,
+    @PackageChangeRepoLog logBuffer: LogBuffer,
+    private val packageInstaller: PackageInstaller,
+) : PackageInstaller.SessionCallback() {
+
+    private val logger = Logger(logBuffer, TAG)
+
+    @GuardedBy("sessions") private val sessions = mutableMapOf<Int, PackageInstallSession>()
+
+    private val _installSessions =
+        MutableStateFlow<List<PackageInstallSession>>(emptyList()).apply {
+            subscriptionCount
+                .map { count -> count > 0 }
+                .distinctUntilChanged()
+                // Drop initial false value
+                .dropWhile { !it }
+                .onEach { isActive ->
+                    if (isActive) {
+                        synchronized(sessions) {
+                            sessions.putAll(
+                                packageInstaller.allSessions
+                                    .map { session -> session.toModel() }
+                                    .associateBy { it.sessionId }
+                            )
+                            updateInstallerSessionsFlow()
+                        }
+                        packageInstaller.registerSessionCallback(
+                            this@PackageInstallerMonitor,
+                            bgHandler
+                        )
+                    } else {
+                        synchronized(sessions) {
+                            sessions.clear()
+                            updateInstallerSessionsFlow()
+                        }
+                        packageInstaller.unregisterSessionCallback(this@PackageInstallerMonitor)
+                    }
+                }
+                .launchIn(bgScope)
+        }
+
+    val installSessionsForPrimaryUser: Flow<List<PackageInstallSession>> =
+        _installSessions.asStateFlow()
+
+    /** Called when a new installer session is created. */
+    override fun onCreated(sessionId: Int) {
+        logger.i({ "session created $int1" }) { int1 = sessionId }
+        updateSession(sessionId)
+    }
+
+    /** Called when new installer session has finished. */
+    override fun onFinished(sessionId: Int, success: Boolean) {
+        logger.i({ "session finished $int1" }) { int1 = sessionId }
+        synchronized(sessions) {
+            sessions.remove(sessionId)
+            updateInstallerSessionsFlow()
+        }
+    }
+
+    /**
+     * Badging details for the session changed. For example, the app icon or label has been updated.
+     */
+    override fun onBadgingChanged(sessionId: Int) {
+        logger.i({ "session badging changed $int1" }) { int1 = sessionId }
+        updateSession(sessionId)
+    }
+
+    /**
+     * A session is considered active when there is ongoing forward progress being made. For
+     * example, a package started downloading.
+     */
+    override fun onActiveChanged(sessionId: Int, active: Boolean) {
+        // Active status updates are not tracked for now
+    }
+
+    override fun onProgressChanged(sessionId: Int, progress: Float) {
+        // Progress updates are not tracked for now
+    }
+
+    private fun updateSession(sessionId: Int) {
+        val session = packageInstaller.getSessionInfo(sessionId)
+
+        synchronized(sessions) {
+            if (session == null) {
+                sessions.remove(sessionId)
+            } else {
+                sessions[sessionId] = session.toModel()
+            }
+            updateInstallerSessionsFlow()
+        }
+    }
+
+    @GuardedBy("sessions")
+    private fun updateInstallerSessionsFlow() {
+        _installSessions.value = sessions.values.toList()
+    }
+
+    companion object {
+        const val TAG = "PackageInstallerMonitor"
+
+        private fun PackageInstaller.SessionInfo.toModel(): PackageInstallSession {
+            return PackageInstallSession(
+                sessionId = this.sessionId,
+                packageName = this.appPackageName,
+                icon = this.getAppIcon(),
+                user = this.user,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/PackageInstallSession.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/src/com/android/systemui/common/shared/model/PackageInstallSession.kt
index bf22563..7025229 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/PackageInstallSession.kt
@@ -14,12 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.common.shared.model
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import android.graphics.Bitmap
+import android.os.UserHandle
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+/** Represents a session of a package being installed on device. */
+data class PackageInstallSession(
+    val sessionId: Int,
+    val packageName: String,
+    val icon: Bitmap?,
+    val user: UserHandle,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
index 9e7fb4e..153b7aa 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
@@ -20,6 +20,8 @@
 import android.app.DreamManager
 import com.android.systemui.CoreStartable
 import com.android.systemui.Flags.communalHub
+import com.android.systemui.Flags.restartDreamOnUnocclude
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -27,8 +29,10 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.sample
+import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 
@@ -43,6 +47,7 @@
     private val powerInteractor: PowerInteractor,
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val communalInteractor: CommunalInteractor,
     private val dreamManager: DreamManager,
     @Background private val bgScope: CoroutineScope,
 ) : CoreStartable {
@@ -52,6 +57,19 @@
             return
         }
 
+        // Return to dream from occluded when not already dreaming.
+        if (restartDreamOnUnocclude()) {
+            keyguardTransitionInteractor.startedKeyguardTransitionStep
+                .sample(keyguardInteractor.isDreaming, ::Pair)
+                .filter {
+                    it.first.from == KeyguardState.OCCLUDED &&
+                        it.first.to == KeyguardState.DREAMING &&
+                        !it.second
+                }
+                .onEach { dreamManager.startDream() }
+                .launchIn(bgScope)
+        }
+
         // Restart the dream underneath the hub in order to support the ability to swipe
         // away the hub to enter the dream.
         keyguardTransitionInteractor.finishedKeyguardState
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index f437032..6f20a8d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.communal
 
 import android.provider.Settings
-import android.service.dreams.Flags.dreamTracksFocus
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.CoreStartable
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -43,6 +42,7 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
@@ -74,6 +74,10 @@
 ) : CoreStartable {
     private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT
 
+    private var timeoutJob: Job? = null
+
+    private var isDreaming: Boolean = false
+
     override fun start() {
         // Handle automatically switching based on keyguard state.
         keyguardTransitionInteractor.startedKeyguardTransitionStep
@@ -113,47 +117,67 @@
             }
             .launchIn(bgScope)
 
-        // Handle timing out back to the dream.
+        // The hub mode timeout should start as soon as the user enters hub mode. At the end of the
+        // timer, if the device is dreaming, hub mode should closed and reveal the dream. If the
+        // dream is not running, nothing will happen. However if the dream starts again underneath
+        // hub mode after the initial timeout expires, such as if the device is docked or the dream
+        // app is updated by the Play store, a new timeout should be started.
         bgScope.launch {
             combine(
                     communalInteractor.desiredScene,
                     // Emit a value on start so the combine starts.
                     communalInteractor.userActivity.emitOnStart()
                 ) { scene, _ ->
-                    // Time out should run whenever we're dreaming and the hub is open, even if not
-                    // docked.
+                    // Only timeout if we're on the hub is open.
                     scene == CommunalScenes.Communal
                 }
-                // mapLatest cancels the previous action block when new values arrive, so any
-                // already running timeout gets cancelled when conditions change or user interaction
-                // is detected.
-                .mapLatest { shouldTimeout ->
-                    if (!shouldTimeout) {
-                        return@mapLatest false
+                .collectLatest { shouldTimeout ->
+                    cancelHubTimeout()
+                    if (shouldTimeout) {
+                        startHubTimeout()
                     }
-
-                    delay(screenTimeout.milliseconds)
-                    true
                 }
-                .sample(keyguardInteractor.isDreaming, ::Pair)
-                .collect { (shouldTimeout, isDreaming) ->
-                    if (isDreaming && shouldTimeout) {
+        }
+        bgScope.launch {
+            keyguardInteractor.isDreaming
+                .sample(communalInteractor.desiredScene, ::Pair)
+                .collectLatest { (isDreaming, scene) ->
+                    this@CommunalSceneStartable.isDreaming = isDreaming
+                    if (scene == CommunalScenes.Communal && isDreaming && timeoutJob == null) {
+                        // If dreaming starts after timeout has expired, ex. if dream restarts under
+                        // the hub, just close the hub immediately.
                         communalInteractor.changeScene(CommunalScenes.Blank)
                     }
                 }
         }
 
-        if (dreamTracksFocus()) {
-            bgScope.launch {
-                communalInteractor.isIdleOnCommunal.collectLatest {
-                    withContext(mainDispatcher) {
-                        notificationShadeWindowController.setGlanceableHubShowing(it)
-                    }
+        bgScope.launch {
+            communalInteractor.isIdleOnCommunal.collectLatest {
+                withContext(mainDispatcher) {
+                    notificationShadeWindowController.setGlanceableHubShowing(it)
                 }
             }
         }
     }
 
+    private fun cancelHubTimeout() {
+        timeoutJob?.cancel()
+        timeoutJob = null
+    }
+
+    private fun startHubTimeout() {
+        if (timeoutJob == null) {
+            timeoutJob =
+                bgScope.launch {
+                    delay(screenTimeout.milliseconds)
+                    if (isDreaming) {
+                        communalInteractor.changeScene(CommunalScenes.Blank)
+                    }
+                    timeoutJob = null
+                }
+        }
+    }
+
     private suspend fun determineSceneAfterTransition(
         lastStartedTransition: TransitionStep,
     ): SceneKey? {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt
index 03f54c8..5cd15f2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt
@@ -17,15 +17,23 @@
 package com.android.systemui.communal.data.model
 
 import android.appwidget.AppWidgetProviderInfo
+import com.android.settingslib.flags.Flags.allowAllWidgetsOnLockscreenByDefault
 
 /**
  * The widget categories to display on communal hub (where categories is a bitfield with values that
  * match those in {@link AppWidgetProviderInfo}).
  */
 @JvmInline
-value class CommunalWidgetCategories(
-    // The default is keyguard widgets.
-    val categories: Int = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
-) {
+value class CommunalWidgetCategories(val categories: Int = defaultCategories) {
     fun contains(category: Int) = (categories and category) == category
+
+    companion object {
+        val defaultCategories: Int
+            get() {
+                return AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD or
+                    if (allowAllWidgetsOnLockscreenByDefault())
+                        AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN
+                    else 0
+            }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
index e2fed6d..e5a0e50 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -53,7 +53,7 @@
                 updateMediaModel(data)
             }
 
-            override fun onMediaDataRemoved(key: String) {
+            override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
                 updateMediaModel()
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
index 40d7440..b27fcfc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
@@ -126,7 +126,7 @@
 
     private fun observeCtaDismissState(user: UserInfo): Flow<Boolean> =
         getSharedPrefsForUser(user)
-            .observe(CTA_DISMISSED_STATE)
+            .observe()
             // Emit at the start of collection to ensure we get an initial value
             .onStart { emit(Unit) }
             .map { getCtaDismissedState() }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
index 9debe0e..88cb64c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -18,7 +18,6 @@
 
 import android.app.admin.DevicePolicyManager
 import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
-import android.appwidget.AppWidgetProviderInfo
 import android.content.IntentFilter
 import android.content.pm.UserInfo
 import android.provider.Settings
@@ -108,10 +107,9 @@
             .onStart { emit(Unit) }
             .map {
                 CommunalWidgetCategories(
-                    // The default is to show only keyguard widgets.
                     secureSettings.getIntForUser(
                         GLANCEABLE_HUB_CONTENT_SETTING,
-                        AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
+                        CommunalWidgetCategories.defaultCategories,
                         user.id
                     )
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index 1f54e70..fdb797d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -17,14 +17,13 @@
 package com.android.systemui.communal.data.repository
 
 import android.app.backup.BackupManager
-import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProviderInfo
 import android.content.ComponentName
 import android.os.UserHandle
-import androidx.annotation.WorkerThread
+import com.android.systemui.common.data.repository.PackageChangeRepository
+import com.android.systemui.common.shared.model.PackageInstallSession
 import com.android.systemui.communal.data.backup.CommunalBackupUtils
-import com.android.systemui.communal.data.db.CommunalItemRank
 import com.android.systemui.communal.data.db.CommunalWidgetDao
-import com.android.systemui.communal.data.db.CommunalWidgetItem
 import com.android.systemui.communal.nano.CommunalHubState
 import com.android.systemui.communal.proto.toCommunalHubState
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
@@ -36,13 +35,15 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
-import com.android.systemui.util.kotlin.getValue
-import java.util.Optional
 import javax.inject.Inject
 import kotlin.coroutines.cancellation.CancellationException
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
@@ -88,7 +89,6 @@
 class CommunalWidgetRepositoryImpl
 @Inject
 constructor(
-    appWidgetManagerOptional: Optional<AppWidgetManager>,
     private val appWidgetHost: CommunalAppWidgetHost,
     @Background private val bgScope: CoroutineScope,
     @Background private val bgDispatcher: CoroutineDispatcher,
@@ -97,6 +97,7 @@
     @CommunalLog logBuffer: LogBuffer,
     private val backupManager: BackupManager,
     private val backupUtils: CommunalBackupUtils,
+    packageChangeRepository: PackageChangeRepository,
 ) : CommunalWidgetRepository {
     companion object {
         const val TAG = "CommunalWidgetRepository"
@@ -104,12 +105,39 @@
 
     private val logger = Logger(logBuffer, TAG)
 
-    private val appWidgetManager by appWidgetManagerOptional
+    /** Widget metadata from database + matching [AppWidgetProviderInfo] if any. */
+    private val widgetEntries: Flow<List<CommunalWidgetEntry>> =
+        combine(
+            communalWidgetDao.getWidgets(),
+            communalWidgetHost.appWidgetProviders,
+        ) { entries, providers ->
+            entries.mapNotNull { (rank, widget) ->
+                CommunalWidgetEntry(
+                    appWidgetId = widget.widgetId,
+                    componentName = widget.componentName,
+                    priority = rank.rank,
+                    providerInfo = providers[widget.widgetId]
+                )
+            }
+        }
 
+    @OptIn(ExperimentalCoroutinesApi::class)
     override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
-        communalWidgetDao
-            .getWidgets()
-            .map { it.mapNotNull(::mapToContentModel) }
+        widgetEntries
+            .flatMapLatest { widgetEntries ->
+                // If and only if any widget is missing provider info, combine with the package
+                // installer sessions flow to check whether they are pending installation. This can
+                // happen after widgets are freshly restored from a backup. In most cases, provider
+                // info is available to all widgets, and is unnecessary to involve an API call to
+                // the package installer.
+                if (widgetEntries.any { it.providerInfo == null }) {
+                    packageChangeRepository.packageInstallSessionsForPrimaryUser.map { sessions ->
+                        widgetEntries.mapNotNull { entry -> mapToContentModel(entry, sessions) }
+                    }
+                } else {
+                    flowOf(widgetEntries.map(::mapToContentModel))
+                }
+            }
             // As this reads from a database and triggers IPCs to AppWidgetManager,
             // it should be executed in the background.
             .flowOn(bgDispatcher)
@@ -245,6 +273,9 @@
                 }
                 appWidgetHost.deleteAppWidgetId(widgetId)
             }
+
+            // Providers may have changed
+            communalWidgetHost.refreshProviders()
         }
     }
 
@@ -255,16 +286,57 @@
         }
     }
 
-    @WorkerThread
-    private fun mapToContentModel(
-        entry: Map.Entry<CommunalItemRank, CommunalWidgetItem>
-    ): CommunalWidgetContentModel? {
-        val (_, widgetId) = entry.value
-        val providerInfo = appWidgetManager?.getAppWidgetInfo(widgetId) ?: return null
-        return CommunalWidgetContentModel(
-            appWidgetId = widgetId,
-            providerInfo = providerInfo,
-            priority = entry.key.rank,
+    /**
+     * Maps a [CommunalWidgetEntry] to a [CommunalWidgetContentModel] with the assumption that the
+     * [AppWidgetProviderInfo] of the entry is available.
+     */
+    private fun mapToContentModel(entry: CommunalWidgetEntry): CommunalWidgetContentModel {
+        return CommunalWidgetContentModel.Available(
+            appWidgetId = entry.appWidgetId,
+            providerInfo = entry.providerInfo!!,
+            priority = entry.priority,
         )
     }
+
+    /**
+     * Maps a [CommunalWidgetEntry] to a [CommunalWidgetContentModel] with a list of install
+     * sessions. If the [AppWidgetProviderInfo] of the entry is absent, and its package is in the
+     * install sessions, the entry is mapped to a pending widget.
+     */
+    private fun mapToContentModel(
+        entry: CommunalWidgetEntry,
+        installSessions: List<PackageInstallSession>,
+    ): CommunalWidgetContentModel? {
+        if (entry.providerInfo != null) {
+            return CommunalWidgetContentModel.Available(
+                appWidgetId = entry.appWidgetId,
+                providerInfo = entry.providerInfo!!,
+                priority = entry.priority,
+            )
+        }
+
+        val session =
+            installSessions.firstOrNull {
+                it.packageName ==
+                    ComponentName.unflattenFromString(entry.componentName)?.packageName
+            }
+        return if (session != null) {
+            CommunalWidgetContentModel.Pending(
+                appWidgetId = entry.appWidgetId,
+                priority = entry.priority,
+                packageName = session.packageName,
+                icon = session.icon,
+                user = session.user,
+            )
+        } else {
+            null
+        }
+    }
+
+    private data class CommunalWidgetEntry(
+        val appWidgetId: Int,
+        val componentName: String,
+        val priority: Int,
+        var providerInfo: AppWidgetProviderInfo? = null,
+    )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 0042915..9599a88 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -60,9 +60,8 @@
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.smartspace.data.repository.SmartspaceRepository
-import com.android.systemui.util.kotlin.BooleanFlowOperators.and
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.not
-import com.android.systemui.util.kotlin.BooleanFlowOperators.or
 import com.android.systemui.util.kotlin.emitOnStart
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -127,10 +126,10 @@
 
     /** Whether communal features are enabled and available. */
     val isCommunalAvailable: Flow<Boolean> =
-        and(
+        allOf(
                 communalSettingsInteractor.isCommunalEnabled,
                 not(keyguardInteractor.isEncryptedOrLockdown),
-                or(keyguardInteractor.isKeyguardShowing, keyguardInteractor.isDreaming)
+                keyguardInteractor.isKeyguardShowing
             )
             .distinctUntilChanged()
             .onEach { available ->
@@ -163,6 +162,13 @@
                 initialValue = false,
             )
 
+    /** Whether to start dreaming when returning from occluded */
+    val dreamFromOccluded: Flow<Boolean> =
+        keyguardTransitionInteractor
+            .transitionStepsToState(KeyguardState.OCCLUDED)
+            .map { it.from == KeyguardState.DREAMING }
+            .stateIn(scope = applicationScope, SharingStarted.Eagerly, false)
+
     /**
      * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene].
      *
@@ -403,19 +409,30 @@
             updateOnWorkProfileBroadcastReceived,
         ) { widgets, allowedCategories, _ ->
             widgets.map { widget ->
-                if (widget.providerInfo.widgetCategory and allowedCategories != 0) {
-                    // At least one category this widget specified is allowed, so show it
-                    WidgetContent.Widget(
-                        appWidgetId = widget.appWidgetId,
-                        providerInfo = widget.providerInfo,
-                        appWidgetHost = appWidgetHost,
-                        inQuietMode = isQuietModeEnabled(widget.providerInfo.profile)
-                    )
-                } else {
-                    WidgetContent.DisabledWidget(
-                        appWidgetId = widget.appWidgetId,
-                        providerInfo = widget.providerInfo,
-                    )
+                when (widget) {
+                    is CommunalWidgetContentModel.Available -> {
+                        if (widget.providerInfo.widgetCategory and allowedCategories != 0) {
+                            // At least one category this widget specified is allowed, so show it
+                            WidgetContent.Widget(
+                                appWidgetId = widget.appWidgetId,
+                                providerInfo = widget.providerInfo,
+                                appWidgetHost = appWidgetHost,
+                                inQuietMode = isQuietModeEnabled(widget.providerInfo.profile)
+                            )
+                        } else {
+                            WidgetContent.DisabledWidget(
+                                appWidgetId = widget.appWidgetId,
+                                providerInfo = widget.providerInfo,
+                            )
+                        }
+                    }
+                    is CommunalWidgetContentModel.Pending -> {
+                        WidgetContent.PendingWidget(
+                            appWidgetId = widget.appWidgetId,
+                            packageName = widget.packageName,
+                            icon = widget.icon,
+                        )
+                    }
                 }
             }
         }
@@ -430,7 +447,15 @@
         } else {
             // Get associated work profile for the currently selected user.
             val workProfile = userTracker.userProfiles.find { it.isManagedProfile }
-            list.filter { it.providerInfo.profile.identifier != workProfile?.id }
+            list.filter { model ->
+                val uid =
+                    when (model) {
+                        is CommunalWidgetContentModel.Available ->
+                            model.providerInfo.profile.identifier
+                        is CommunalWidgetContentModel.Pending -> model.user.identifier
+                    }
+                uid != workProfile?.id
+            }
         }
 
     /** A flow of available smartspace targets. Currently only showing timers. */
@@ -513,7 +538,11 @@
     ): List<CommunalWidgetContentModel> {
         val currentUserIds = userTracker.userProfiles.map { it.id }.toSet()
         return list.filter { widget ->
-            currentUserIds.contains(widget.providerInfo.profile?.identifier)
+            when (widget) {
+                is CommunalWidgetContentModel.Available ->
+                    currentUserIds.contains(widget.providerInfo.profile?.identifier)
+                is CommunalWidgetContentModel.Pending -> true
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
index f9de609..3e5126a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
@@ -75,7 +75,7 @@
                 scope = bgScope,
                 // Start this eagerly since the value can be accessed synchronously.
                 started = SharingStarted.Eagerly,
-                initialValue = CommunalWidgetCategories().categories
+                initialValue = CommunalWidgetCategories.defaultCategories
             )
 
     private val workProfileUserInfoCallbackFlow: Flow<UserInfo?> = conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 7061227..122240d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -19,6 +19,7 @@
 import android.appwidget.AppWidgetProviderInfo
 import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
 import android.content.pm.ApplicationInfo
+import android.graphics.Bitmap
 import android.widget.RemoteViews
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.widgets.CommunalAppWidgetHost
@@ -45,11 +46,10 @@
 
     sealed interface WidgetContent : CommunalContentModel {
         val appWidgetId: Int
-        val providerInfo: AppWidgetProviderInfo
 
         data class Widget(
             override val appWidgetId: Int,
-            override val providerInfo: AppWidgetProviderInfo,
+            val providerInfo: AppWidgetProviderInfo,
             val appWidgetHost: CommunalAppWidgetHost,
             val inQuietMode: Boolean,
         ) : WidgetContent {
@@ -66,7 +66,7 @@
 
         data class DisabledWidget(
             override val appWidgetId: Int,
-            override val providerInfo: AppWidgetProviderInfo
+            val providerInfo: AppWidgetProviderInfo
         ) : WidgetContent {
             override val key = KEY.disabledWidget(appWidgetId)
             // Widget size is always half.
@@ -75,6 +75,16 @@
             val appInfo: ApplicationInfo?
                 get() = providerInfo.providerInfo?.applicationInfo
         }
+
+        data class PendingWidget(
+            override val appWidgetId: Int,
+            val packageName: String,
+            val icon: Bitmap? = null,
+        ) : WidgetContent {
+            override val key = KEY.pendingWidget(appWidgetId)
+            // Widget size is always half.
+            override val size = CommunalContentSize.HALF
+        }
     }
 
     /** A placeholder item representing a new widget being added */
@@ -127,6 +137,10 @@
                 return "disabled_widget_$id"
             }
 
+            fun pendingWidget(id: Int): String {
+                return "pending_widget_$id"
+            }
+
             fun widgetPlaceholder(): String {
                 return "widget_placeholder_${UUID.randomUUID()}"
             }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
index e141dc4..53aecc1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
@@ -17,10 +17,27 @@
 package com.android.systemui.communal.shared.model
 
 import android.appwidget.AppWidgetProviderInfo
+import android.graphics.Bitmap
+import android.os.UserHandle
 
 /** Encapsulates data for a communal widget. */
-data class CommunalWidgetContentModel(
-    val appWidgetId: Int,
-    val providerInfo: AppWidgetProviderInfo,
-    val priority: Int,
-)
+sealed interface CommunalWidgetContentModel {
+    val appWidgetId: Int
+    val priority: Int
+
+    /** Widget is ready to display */
+    data class Available(
+        override val appWidgetId: Int,
+        val providerInfo: AppWidgetProviderInfo,
+        override val priority: Int,
+    ) : CommunalWidgetContentModel
+
+    /** Widget is pending installation */
+    data class Pending(
+        override val appWidgetId: Int,
+        override val priority: Int,
+        val packageName: String,
+        val icon: Bitmap?,
+        val user: UserHandle,
+    ) : CommunalWidgetContentModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 3f92223..650852c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -35,7 +35,6 @@
 import com.android.systemui.log.dagger.CommunalLog
 import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.media.dagger.MediaModule
-import com.android.systemui.res.R
 import javax.inject.Inject
 import javax.inject.Named
 import kotlinx.coroutines.CoroutineDispatcher
@@ -96,6 +95,8 @@
         uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL)
     }
 
+    val isIdleOnCommunal: StateFlow<Boolean> = communalInteractor.isIdleOnCommunal
+
     /** Launch the widget picker activity using the given {@link ActivityResultLauncher}. */
     suspend fun onOpenWidgetPicker(
         resources: Resources,
@@ -104,7 +105,12 @@
     ): Boolean =
         withContext(backgroundDispatcher) {
             val widgets = communalInteractor.widgetContent.first()
-            val excludeList = widgets.mapTo(ArrayList()) { it.providerInfo }
+            val excludeList =
+                widgets.filterIsInstance<CommunalContentModel.WidgetContent.Widget>().mapTo(
+                    ArrayList()
+                ) {
+                    it.providerInfo
+                }
             getWidgetPickerActivityIntent(resources, packageManager, excludeList)?.let {
                 try {
                     activityLauncher.launch(it)
@@ -131,14 +137,6 @@
         return Intent(Intent.ACTION_PICK).apply {
             setPackage(packageName)
             putExtra(
-                EXTRA_DESIRED_WIDGET_WIDTH,
-                resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_width)
-            )
-            putExtra(
-                EXTRA_DESIRED_WIDGET_HEIGHT,
-                resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_height)
-            )
-            putExtra(
                 AppWidgetManager.EXTRA_CATEGORY_FILTER,
                 communalSettingsInteractor.communalWidgetCategories.value
             )
@@ -163,8 +161,6 @@
     companion object {
         private const val TAG = "CommunalEditModeViewModel"
 
-        private const val EXTRA_DESIRED_WIDGET_WIDTH = "desired_widget_width"
-        private const val EXTRA_DESIRED_WIDGET_HEIGHT = "desired_widget_height"
         private const val EXTRA_UI_SURFACE_KEY = "ui_surface"
         private const val EXTRA_UI_SURFACE_VALUE = "widgets_hub"
         const val EXTRA_ADDED_APP_WIDGETS_KEY = "added_app_widgets"
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
index 337d873..9114aab 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
@@ -33,6 +33,7 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 
 /** View model for transitions related to the communal hub. */
@@ -49,6 +50,27 @@
     communalInteractor: CommunalInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
 ) {
+    // Show UMO on glanceable hub immediately on transition into glanceable hub
+    private val showUmoFromOccludedToGlanceableHub: Flow<Boolean> =
+        keyguardTransitionInteractor
+            .transitionStepsFromState(KeyguardState.OCCLUDED)
+            .filter {
+                it.to == KeyguardState.GLANCEABLE_HUB &&
+                    (it.transitionState == TransitionState.STARTED ||
+                        it.transitionState == TransitionState.CANCELED)
+            }
+            .map { it.transitionState == TransitionState.STARTED }
+
+    private val showUmoFromGlanceableHubToOccluded: Flow<Boolean> =
+        keyguardTransitionInteractor
+            .transitionStepsFromState(KeyguardState.GLANCEABLE_HUB)
+            .filter {
+                it.to == KeyguardState.OCCLUDED &&
+                    (it.transitionState == TransitionState.FINISHED ||
+                        it.transitionState == TransitionState.CANCELED)
+            }
+            .map { it.transitionState != TransitionState.FINISHED }
+
     /**
      * Whether UMO location should be on communal. This flow is responsive to transitions so that a
      * new value is emitted at the right step of a transition to/from communal hub that the location
@@ -60,6 +82,8 @@
                 glanceableHubToLockscreenTransitionViewModel.showUmo,
                 dreamToGlanceableHubTransitionViewModel.showUmo,
                 glanceableHubToDreamTransitionViewModel.showUmo,
+                showUmoFromOccludedToGlanceableHub,
+                showUmoFromGlanceableHubToOccluded,
             )
             .distinctUntilChanged()
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 1120466..97db43b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.log.LogBuffer
@@ -64,10 +64,10 @@
     @Application private val scope: CoroutineScope,
     @Main private val resources: Resources,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    keyguardInteractor: KeyguardInteractor,
     private val communalInteractor: CommunalInteractor,
     tutorialInteractor: CommunalTutorialInteractor,
     private val shadeInteractor: ShadeInteractor,
-    deviceEntryInteractor: DeviceEntryInteractor,
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
     @CommunalLog logBuffer: LogBuffer,
 ) : BaseCommunalViewModel(communalInteractor, mediaHost) {
@@ -142,8 +142,6 @@
     val isEnableWorkProfileDialogShowing: Flow<Boolean> =
         _isEnableWorkProfileDialogShowing.asStateFlow()
 
-    val deviceUnlocked: Flow<Boolean> = deviceEntryInteractor.isUnlocked
-
     init {
         // Initialize our media host for the UMO. This only needs to happen once and must be done
         // before the MediaHierarchyManager attempts to move the UMO to the hub.
@@ -240,6 +238,14 @@
      */
     val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded)
 
+    // TODO(b/339667383): remove this temporary swipe gesture handle
+    /**
+     * The dream overlay has its own gesture handle as the SysUI window is not visible above the
+     * dream. This flow will be false when dreaming so that we don't show a duplicate handle when
+     * opening the hub over the dream.
+     */
+    val showGestureIndicator: Flow<Boolean> = not(keyguardInteractor.isDreaming)
+
     companion object {
         const val POPUP_AUTO_HIDE_TIMEOUT_MS = 12000L
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
index 5f1d89e..b7e8205 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
@@ -24,6 +24,7 @@
 import android.widget.RemoteViews
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
+import javax.annotation.concurrent.GuardedBy
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharedFlow
@@ -47,6 +48,8 @@
     /** App widget ids that have been removed and no longer available. */
     val appWidgetIdToRemove: SharedFlow<Int> = _appWidgetIdToRemove.asSharedFlow()
 
+    @GuardedBy("observers") private val observers = mutableSetOf<Observer>()
+
     override fun onCreateView(
         context: Context,
         appWidgetId: Int,
@@ -77,6 +80,61 @@
         }
     }
 
+    override fun allocateAppWidgetId(): Int {
+        return super.allocateAppWidgetId().also { appWidgetId ->
+            backgroundScope.launch {
+                observers.forEach { observer -> observer.onAllocateAppWidgetId(appWidgetId) }
+            }
+        }
+    }
+
+    override fun deleteAppWidgetId(appWidgetId: Int) {
+        super.deleteAppWidgetId(appWidgetId)
+        backgroundScope.launch {
+            observers.forEach { observer -> observer.onDeleteAppWidgetId(appWidgetId) }
+        }
+    }
+
+    override fun startListening() {
+        super.startListening()
+        backgroundScope.launch { observers.forEach { observer -> observer.onHostStartListening() } }
+    }
+
+    override fun stopListening() {
+        super.stopListening()
+        backgroundScope.launch { observers.forEach { observer -> observer.onHostStopListening() } }
+    }
+
+    fun addObserver(observer: Observer) {
+        synchronized(observers) { observers.add(observer) }
+    }
+
+    fun removeObserver(observer: Observer) {
+        synchronized(observers) { observers.remove(observer) }
+    }
+
+    /**
+     * Allows another class to observe the [CommunalAppWidgetHost] and handle any logic there.
+     *
+     * This is mainly for testability as it is difficult to test a real instance of [AppWidgetHost]
+     * which communicates with framework services.
+     *
+     * Note: all the callbacks are launched from the background scope.
+     */
+    interface Observer {
+        /** Called immediately after the host has started listening for widget updates. */
+        fun onHostStartListening() {}
+
+        /** Called immediately after the host has stopped listening for widget updates. */
+        fun onHostStopListening() {}
+
+        /** Called immediately after a new app widget id has been allocated. */
+        fun onAllocateAppWidgetId(appWidgetId: Int) {}
+
+        /** Called immediately after an app widget id is to be deleted. */
+        fun onDeleteAppWidgetId(appWidgetId: Int) {}
+    }
+
     companion object {
         private const val TAG = "CommunalAppWidgetHost"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
index 8390d62..301da51 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
@@ -23,7 +23,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.settings.UserTracker
-import com.android.systemui.util.kotlin.BooleanFlowOperators.or
+import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
 import com.android.systemui.util.kotlin.pairwise
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
@@ -39,6 +39,7 @@
 @Inject
 constructor(
     private val appWidgetHost: CommunalAppWidgetHost,
+    private val communalWidgetHost: CommunalWidgetHost,
     private val communalInteractor: CommunalInteractor,
     private val userTracker: UserTracker,
     @Background private val bgScope: CoroutineScope,
@@ -46,7 +47,7 @@
 ) : CoreStartable {
 
     override fun start() {
-        or(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen)
+        anyOf(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen)
             // Only trigger updates on state changes, ignoring the initial false value.
             .pairwise(false)
             .filter { (previous, new) -> previous != new }
@@ -70,9 +71,11 @@
         // Always ensure this is called on the main/ui thread.
         withContext(uiDispatcher) {
             if (active) {
+                communalWidgetHost.startObservingHost()
                 appWidgetHost.startListening()
             } else {
                 appWidgetHost.stopListening()
+                communalWidgetHost.stopObservingHost()
             }
         }
 
@@ -83,7 +86,15 @@
     private fun validateWidgetsAndDeleteOrphaned(widgets: List<CommunalWidgetContentModel>) {
         val currentUserIds = userTracker.userProfiles.map { it.id }.toSet()
         widgets
-            .filter { widget -> !currentUserIds.contains(widget.providerInfo.profile?.identifier) }
+            .filter { widget ->
+                val uid =
+                    when (widget) {
+                        is CommunalWidgetContentModel.Available ->
+                            widget.providerInfo.profile?.identifier
+                        is CommunalWidgetContentModel.Pending -> widget.user.identifier
+                    }
+                !currentUserIds.contains(uid)
+            }
             .onEach { widget -> communalInteractor.deleteWidget(id = widget.appWidgetId) }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
index 840c3a8..2559137 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.communal.widgets
 
 import android.appwidget.AppWidgetHostView
+import android.appwidget.AppWidgetProviderInfo
 import android.content.Context
 import android.graphics.Outline
 import android.graphics.Rect
@@ -50,6 +51,11 @@
         enforceRoundedCorners()
     }
 
+    override fun setAppWidget(appWidgetId: Int, info: AppWidgetProviderInfo?) {
+        super.setAppWidget(appWidgetId, info)
+        setPadding(0, 0, 0, 0)
+    }
+
     private val cornerRadiusEnforcementOutline: ViewOutlineProvider =
         object : ViewOutlineProvider() {
             override fun getOutline(view: View?, outline: Outline) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt
index 93e2b37..42107c1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal.widgets
 
+import android.appwidget.AppWidgetHost.AppWidgetHostListener
 import android.appwidget.AppWidgetManager
 import android.appwidget.AppWidgetProviderInfo
 import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL
@@ -23,6 +24,9 @@
 import android.content.ComponentName
 import android.os.Bundle
 import android.os.UserHandle
+import android.widget.RemoteViews
+import androidx.annotation.WorkerThread
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
@@ -30,6 +34,11 @@
 import com.android.systemui.util.kotlin.getOrNull
 import java.util.Optional
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
 
 /**
  * Widget host that interacts with AppWidget service and host to bind and provide info for widgets
@@ -38,11 +47,12 @@
 class CommunalWidgetHost
 @Inject
 constructor(
+    @Background private val bgScope: CoroutineScope,
     private val appWidgetManager: Optional<AppWidgetManager>,
     private val appWidgetHost: CommunalAppWidgetHost,
     private val selectedUserInteractor: SelectedUserInteractor,
     @CommunalLog logBuffer: LogBuffer,
-) {
+) : CommunalAppWidgetHost.Observer {
     companion object {
         private const val TAG = "CommunalWidgetHost"
 
@@ -60,6 +70,19 @@
 
     private val logger = Logger(logBuffer, TAG)
 
+    private val _appWidgetProviders = MutableStateFlow(emptyMap<Int, AppWidgetProviderInfo?>())
+
+    /**
+     * A flow of mappings between an appWidgetId and its corresponding [AppWidgetProviderInfo].
+     * These [AppWidgetProviderInfo]s represent app widgets that are actively bound to the
+     * [CommunalAppWidgetHost].
+     *
+     * The [AppWidgetProviderInfo] may be null in the case that the widget is bound but its provider
+     * is unavailable. For example, its package is not installed.
+     */
+    val appWidgetProviders: StateFlow<Map<Int, AppWidgetProviderInfo?>> =
+        _appWidgetProviders.asStateFlow()
+
     /**
      * Allocate an app widget id and binds the widget with the provider and associated user.
      *
@@ -77,6 +100,7 @@
             )
         ) {
             logger.d("Successfully bound the widget $provider")
+            onProviderInfoUpdated(id, getAppWidgetInfo(id))
             return id
         }
         appWidgetHost.deleteAppWidgetId(id)
@@ -100,7 +124,83 @@
         return false
     }
 
+    @WorkerThread
     fun getAppWidgetInfo(widgetId: Int): AppWidgetProviderInfo? {
         return appWidgetManager.getOrNull()?.getAppWidgetInfo(widgetId)
     }
+
+    fun startObservingHost() {
+        appWidgetHost.addObserver(this@CommunalWidgetHost)
+    }
+
+    fun stopObservingHost() {
+        appWidgetHost.removeObserver(this@CommunalWidgetHost)
+    }
+
+    fun refreshProviders() {
+        bgScope.launch {
+            val newProviders = mutableMapOf<Int, AppWidgetProviderInfo?>()
+            appWidgetHost.appWidgetIds.forEach { appWidgetId ->
+                // Listen for updates from each bound widget
+                addListener(appWidgetId)
+
+                // Fetch provider info of the widget
+                newProviders[appWidgetId] = getAppWidgetInfo(appWidgetId)
+            }
+
+            _appWidgetProviders.value = newProviders.toMap()
+        }
+    }
+
+    override fun onHostStartListening() {
+        refreshProviders()
+    }
+
+    override fun onHostStopListening() {
+        // Remove listeners
+        _appWidgetProviders.value.keys.forEach { appWidgetId ->
+            appWidgetHost.removeListener(appWidgetId)
+        }
+
+        // Clear providers
+        _appWidgetProviders.value = emptyMap()
+    }
+
+    override fun onAllocateAppWidgetId(appWidgetId: Int) {
+        addListener(appWidgetId)
+    }
+
+    override fun onDeleteAppWidgetId(appWidgetId: Int) {
+        appWidgetHost.removeListener(appWidgetId)
+        _appWidgetProviders.value =
+            _appWidgetProviders.value.toMutableMap().also { it.remove(appWidgetId) }
+    }
+
+    private fun addListener(appWidgetId: Int) {
+        appWidgetHost.setListener(
+            appWidgetId,
+            CommunalAppWidgetHostListener(appWidgetId, this::onProviderInfoUpdated),
+        )
+    }
+
+    private fun onProviderInfoUpdated(appWidgetId: Int, providerInfo: AppWidgetProviderInfo?) {
+        bgScope.launch {
+            _appWidgetProviders.value =
+                _appWidgetProviders.value.toMutableMap().also { it[appWidgetId] = providerInfo }
+        }
+    }
+
+    /** A [AppWidgetHostListener] for [appWidgetId]. */
+    private class CommunalAppWidgetHostListener(
+        private val appWidgetId: Int,
+        private val onUpdateProviderInfo: (Int, AppWidgetProviderInfo?) -> Unit,
+    ) : AppWidgetHostListener {
+        override fun onUpdateProviderInfo(providerInfo: AppWidgetProviderInfo?) {
+            onUpdateProviderInfo(appWidgetId, providerInfo)
+        }
+
+        override fun onViewDataChanged(viewId: Int) {}
+
+        override fun updateAppWidget(remoteViews: RemoteViews?) {}
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
index aa6516d..2000f96 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
@@ -69,16 +69,18 @@
         @SysUISingleton
         @Provides
         fun provideCommunalWidgetHost(
+            @Application applicationScope: CoroutineScope,
             appWidgetManager: Optional<AppWidgetManager>,
             appWidgetHost: CommunalAppWidgetHost,
             selectedUserInteractor: SelectedUserInteractor,
             @CommunalLog logBuffer: LogBuffer,
         ): CommunalWidgetHost {
             return CommunalWidgetHost(
+                applicationScope,
                 appWidgetManager,
                 appWidgetHost,
                 selectedUserInteractor,
-                logBuffer
+                logBuffer,
             )
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index f20fafc..426f484 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -44,6 +44,7 @@
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
 import javax.inject.Inject
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
 
 /** An Activity for editing the widgets that appear in hub mode. */
@@ -69,6 +70,8 @@
 
     private var shouldOpenWidgetPickerOnStart = false
 
+    private var lockOnDestroy = false
+
     private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> =
         registerForActivityResult(StartActivityForResult()) { result ->
             when (result.resultCode) {
@@ -149,15 +152,18 @@
     }
 
     private fun onEditDone() {
-        try {
+        lifecycleScope.launch {
             communalViewModel.changeScene(
                 CommunalScenes.Communal,
                 CommunalTransitionKeys.SimpleFade
             )
-            checkNotNull(windowManagerService).lockNow(/* options */ null)
+
+            // Wait for the current scene to be idle on communal.
+            communalViewModel.isIdleOnCommunal.first { it }
+            // Then finish the activity (this helps to avoid a flash of lockscreen when locking
+            // in onDestroy()).
+            lockOnDestroy = true
             finish()
-        } catch (e: RemoteException) {
-            Log.e(TAG, "Couldn't lock the device as WindowManager is dead.")
         }
     }
 
@@ -190,5 +196,15 @@
     override fun onDestroy() {
         super.onDestroy()
         communalViewModel.setEditModeOpen(false)
+
+        if (lockOnDestroy) lockNow()
+    }
+
+    private fun lockNow() {
+        try {
+            checkNotNull(windowManagerService).lockNow(/* options */ null)
+        } catch (e: RemoteException) {
+            Log.e(TAG, "Couldn't lock the device as WindowManager is dead.")
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt
deleted file mode 100644
index 4e40042..0000000
--- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.contrast
-
-import android.app.Activity
-import android.os.Bundle
-import javax.inject.Inject
-
-/** Trampoline activity responsible for creating a [ContrastDialogDelegate] */
-class ContrastDialogActivity
-@Inject
-constructor(
-    private val contrastDialogDelegate : ContrastDialogDelegate
-) : Activity() {
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        contrastDialogDelegate.createDialog().show()
-        finish()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt
deleted file mode 100644
index 0daa058..0000000
--- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.contrast
-
-import android.app.UiModeManager
-import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_HIGH
-import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_MEDIUM
-import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_STANDARD
-import android.app.UiModeManager.ContrastUtils.fromContrastLevel
-import android.app.UiModeManager.ContrastUtils.toContrastLevel
-import android.os.Bundle
-import android.provider.Settings
-import android.view.View
-import android.widget.FrameLayout
-import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.res.R
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.util.settings.SecureSettings
-import java.util.concurrent.Executor
-import javax.inject.Inject
-
-/** Dialog to select contrast options */
-class ContrastDialogDelegate
-@Inject
-constructor(
-    private val sysuiDialogFactory: SystemUIDialog.Factory,
-    @Main private val mainExecutor: Executor,
-    private val uiModeManager: UiModeManager,
-    private val userTracker: UserTracker,
-    private val secureSettings: SecureSettings,
-) : SystemUIDialog.Delegate, UiModeManager.ContrastChangeListener {
-
-    @VisibleForTesting lateinit var contrastButtons: Map<Int, FrameLayout>
-    lateinit var dialogView: View
-    @VisibleForTesting var initialContrast: Float = fromContrastLevel(CONTRAST_LEVEL_STANDARD)
-
-    override fun createDialog(): SystemUIDialog {
-        val dialog = sysuiDialogFactory.create(this)
-        dialogView = dialog.layoutInflater.inflate(R.layout.contrast_dialog, null)
-        with(dialog) {
-            setView(dialogView)
-
-            setTitle(R.string.quick_settings_contrast_label)
-            setNeutralButton(R.string.cancel) { _, _ ->
-                secureSettings.putFloatForUser(
-                    Settings.Secure.CONTRAST_LEVEL,
-                    initialContrast,
-                    userTracker.userId
-                )
-                dialog.dismiss()
-            }
-            setPositiveButton(com.android.settingslib.R.string.done) { _, _ -> dialog.dismiss() }
-        }
-
-        return dialog
-    }
-
-    override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
-        contrastButtons =
-            mapOf(
-                CONTRAST_LEVEL_STANDARD to dialog.requireViewById(R.id.contrast_button_standard),
-                CONTRAST_LEVEL_MEDIUM to dialog.requireViewById(R.id.contrast_button_medium),
-                CONTRAST_LEVEL_HIGH to dialog.requireViewById(R.id.contrast_button_high)
-            )
-
-        contrastButtons.forEach { (contrastLevel, contrastButton) ->
-            contrastButton.setOnClickListener {
-                val contrastValue = fromContrastLevel(contrastLevel)
-                secureSettings.putFloatForUser(
-                    Settings.Secure.CONTRAST_LEVEL,
-                    contrastValue,
-                    userTracker.userId
-                )
-            }
-        }
-
-        initialContrast = uiModeManager.contrast
-        highlightContrast(toContrastLevel(initialContrast))
-    }
-
-    override fun onStart(dialog: SystemUIDialog) {
-        uiModeManager.addContrastChangeListener(mainExecutor, this)
-    }
-
-    override fun onStop(dialog: SystemUIDialog) {
-        uiModeManager.removeContrastChangeListener(this)
-    }
-
-    override fun onContrastChanged(contrast: Float) {
-        highlightContrast(toContrastLevel(contrast))
-    }
-
-    private fun highlightContrast(contrast: Int) {
-        contrastButtons.forEach { (level, button) -> button.isSelected = level == contrast }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
index 7c2dae3..060a331 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
@@ -25,10 +25,10 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
 import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
+import com.android.systemui.util.kotlin.emitOnStart
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
 
 class AuthorizedPanelsRepositoryImpl
 @Inject
@@ -40,7 +40,7 @@
 
     override fun observeAuthorizedPanels(user: UserHandle): Flow<Set<String>> {
         val prefs = instantiateSharedPrefs(user)
-        return prefs.observe(KEY).onStart { emit(Unit) }.map { getAuthorizedPanelsInternal(prefs) }
+        return prefs.observe().emitOnStart().map { getAuthorizedPanelsInternal(prefs) }
     }
 
     override fun getAuthorizedPanels(): Set<String> {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
index 9be04940..691ec76 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
@@ -26,12 +26,12 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
 import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
+import com.android.systemui.util.kotlin.emitOnStart
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
 
 @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
 @SysUISingleton
@@ -63,8 +63,8 @@
     ): Flow<SelectedComponentRepository.SelectedComponent?> {
         val prefs = getSharedPreferencesForUser(userHandle.identifier)
         return prefs
-            .observe(PREF_COMPONENT)
-            .onStart { emit(Unit) }
+            .observe()
+            .emitOnStart()
             .map { getSelectedComponent(userHandle) }
             .flowOn(bgDispatcher)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index d2df276..c2e1e33 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -20,7 +20,6 @@
 
 import com.android.systemui.ForegroundServicesDialog;
 import com.android.systemui.communal.widgets.EditWidgetsActivity;
-import com.android.systemui.contrast.ContrastDialogActivity;
 import com.android.systemui.keyguard.WorkLockActivity;
 import com.android.systemui.people.PeopleSpaceActivity;
 import com.android.systemui.people.widget.LaunchConversationActivity;
@@ -72,12 +71,6 @@
     @ClassKey(BrightnessDialog.class)
     public abstract Activity bindBrightnessDialog(BrightnessDialog activity);
 
-    /** Inject into ContrastDialogActivity. */
-    @Binds
-    @IntoMap
-    @ClassKey(ContrastDialogActivity.class)
-    public abstract Activity bindContrastDialogActivity(ContrastDialogActivity activity);
-
     /** Inject into UsbDebuggingActivity. */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index ef3f10f..11e6f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -49,6 +49,7 @@
 import android.content.om.OverlayManager;
 import android.content.pm.IPackageManager;
 import android.content.pm.LauncherApps;
+import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.ShortcutManager;
 import android.content.res.AssetManager;
@@ -224,6 +225,13 @@
 
     @Provides
     @Singleton
+    static UserScopedService<ColorDisplayManager> provideScopedColorDisplayManager(
+            Context context) {
+        return new UserScopedServiceImpl<>(context, ColorDisplayManager.class);
+    }
+
+    @Provides
+    @Singleton
     static CrossWindowBlurListeners provideCrossWindowBlurListeners() {
         return CrossWindowBlurListeners.getInstance();
     }
@@ -483,6 +491,12 @@
 
     @Provides
     @Singleton
+    static PackageInstaller providePackageInstaller(PackageManager packageManager) {
+        return packageManager.getPackageInstaller();
+    }
+
+    @Provides
+    @Singleton
     static PackageManagerWrapper providePackageManagerWrapper() {
         return PackageManagerWrapper.getInstance();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index ba45a51..813fccf 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -46,9 +46,9 @@
 import com.android.systemui.keyguard.data.repository.FaceAuthTableLog
 import com.android.systemui.keyguard.data.repository.FaceDetectTableLog
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions
@@ -64,6 +64,7 @@
 import com.google.errorprone.annotations.CompileTimeConstant
 import java.io.PrintWriter
 import java.util.Arrays
+import java.util.concurrent.Executor
 import java.util.stream.Collectors
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -150,12 +151,12 @@
     @Application private val applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
+    @Background private val backgroundExecutor: Executor,
     private val sessionTracker: SessionTracker,
     private val uiEventsLogger: UiEventLogger,
     private val faceAuthLogger: FaceAuthenticationLogger,
     private val biometricSettingsRepository: BiometricSettingsRepository,
     private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
-    trustRepository: TrustRepository,
     private val keyguardRepository: KeyguardRepository,
     private val powerInteractor: PowerInteractor,
     private val keyguardInteractor: KeyguardInteractor,
@@ -235,7 +236,10 @@
         }
 
     init {
-        faceManager?.addLockoutResetCallback(faceLockoutResetCallback)
+        backgroundExecutor.execute {
+            faceManager?.addLockoutResetCallback(faceLockoutResetCallback)
+            faceAuthLogger.addLockoutResetCallbackDone()
+        }
         faceAcquiredInfoIgnoreList =
             Arrays.stream(
                     context.resources.getIntArray(
@@ -299,7 +303,7 @@
 
     private fun listenForSchedulingWatchdog() {
         keyguardTransitionInteractor
-            .transition(to = KeyguardState.GONE)
+            .transition(Edge.create(to = KeyguardState.GONE))
             .filter { it.transitionState == TransitionState.FINISHED }
             .onEach {
                 // We deliberately want to run this in background because scheduleWatchdog does
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 662974d..d079a95 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -240,6 +240,15 @@
     }
 
     /**
+     * Whether the lockscreen is enabled for the current user. This is `true` whenever the user has
+     * chosen any secure authentication method and even if they set the lockscreen to be dismissed
+     * when the user swipes on it.
+     */
+    suspend fun isLockscreenEnabled(): Boolean {
+        return repository.isLockscreenEnabled()
+    }
+
+    /**
      * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically
      * dismissed once the authentication challenge is completed. For example, completing a biometric
      * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index 6c6683a..669cd94 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.DevicePosture
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
@@ -126,9 +127,9 @@
             .launchIn(applicationScope)
 
         merge(
-                keyguardTransitionInteractor.transition(AOD, LOCKSCREEN),
-                keyguardTransitionInteractor.transition(OFF, LOCKSCREEN),
-                keyguardTransitionInteractor.transition(DOZING, LOCKSCREEN),
+                keyguardTransitionInteractor.transition(Edge.create(AOD, LOCKSCREEN)),
+                keyguardTransitionInteractor.transition(Edge.create(OFF, LOCKSCREEN)),
+                keyguardTransitionInteractor.transition(Edge.create(DOZING, LOCKSCREEN)),
             )
             .filter { it.transitionState == TransitionState.STARTED }
             .sample(powerInteractor.detailedWakefulness)
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
index 0fd6887..8c3de4b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
@@ -89,8 +89,8 @@
                 dozeFalsingManagerAdapter,
                 dozeTriggers,
                 dozeUi,
-                dozeScreenState,
                 dozeScreenBrightness,
+                dozeScreenState,
                 dozeWallpaperState,
                 dozeDockHandler,
                 dozeAuthRemover,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 8c0a73c..1e725eb 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -16,15 +16,21 @@
 
 package com.android.systemui.dreams;
 
+import static android.service.dreams.Flags.dreamHandlesBeingObscured;
+
 import static com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress;
 import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamAlphaScaledExpansion;
 import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamYPositionScaledExpansion;
+import static com.android.systemui.Flags.communalHub;
+import static com.android.systemui.Flags.glanceableHubGestureHandle;
 import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_BOTTOM;
 import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_TOP;
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
 
 import android.animation.Animator;
+import android.app.DreamManager;
 import android.content.res.Resources;
 import android.graphics.Region;
 import android.os.Handler;
@@ -37,7 +43,9 @@
 import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.complication.ComplicationHostViewController;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.dreams.dagger.DreamOverlayModule;
@@ -45,10 +53,12 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.res.R;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.util.ViewController;
 
 import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.flow.FlowKt;
 
 import java.util.Arrays;
 
@@ -68,6 +78,8 @@
     private final DreamOverlayStateController mStateController;
     private final LowLightTransitionCoordinator mLowLightTransitionCoordinator;
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    private final ShadeInteractor mShadeInteractor;
+    private final CommunalInteractor mCommunalInteractor;
 
     private final ComplicationHostViewController mComplicationHostViewController;
 
@@ -87,9 +99,10 @@
 
     // Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates).
     private final Handler mHandler;
-    private final CoroutineDispatcher mMainDispatcher;
+    private final CoroutineDispatcher mBackgroundDispatcher;
     private final int mDreamOverlayMaxTranslationY;
     private final PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
+    private final DreamManager mDreamManager;
 
     private long mJitterStartTimeMillis;
 
@@ -174,11 +187,12 @@
             DreamOverlayContainerView containerView,
             ComplicationHostViewController complicationHostViewController,
             @Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView,
+            @Named(DreamOverlayModule.HUB_GESTURE_INDICATOR_VIEW) View hubGestureIndicatorView,
             DreamOverlayStatusBarViewController statusBarViewController,
             LowLightTransitionCoordinator lowLightTransitionCoordinator,
             BlurUtils blurUtils,
             @Main Handler handler,
-            @Main CoroutineDispatcher mainDispatcher,
+            @Background CoroutineDispatcher backgroundDispatcher,
             @Main Resources resources,
             @Named(DreamOverlayModule.MAX_BURN_IN_OFFSET) int maxBurnInOffset,
             @Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long
@@ -188,23 +202,33 @@
             DreamOverlayAnimationsController animationsController,
             DreamOverlayStateController stateController,
             BouncerlessScrimController bouncerlessScrimController,
-            KeyguardTransitionInteractor keyguardTransitionInteractor) {
+            KeyguardTransitionInteractor keyguardTransitionInteractor,
+            ShadeInteractor shadeInteractor,
+            CommunalInteractor communalInteractor,
+            DreamManager dreamManager) {
         super(containerView);
         mDreamOverlayContentView = contentView;
         mStatusBarViewController = statusBarViewController;
         mBlurUtils = blurUtils;
         mDreamOverlayAnimationsController = animationsController;
         mStateController = stateController;
+        mCommunalInteractor = communalInteractor;
         mLowLightTransitionCoordinator = lowLightTransitionCoordinator;
 
         mBouncerlessScrimController = bouncerlessScrimController;
-        mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback);
 
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+        mShadeInteractor = shadeInteractor;
 
         mComplicationHostViewController = complicationHostViewController;
         mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize(
                 R.dimen.dream_overlay_y_offset);
+
+        if (communalHub() && glanceableHubGestureHandle()) {
+            // TODO(b/339667383): remove this temporary swipe gesture handle
+            hubGestureIndicatorView.setVisibility(View.VISIBLE);
+        }
+
         final View view = mComplicationHostViewController.getView();
 
         mDreamOverlayContentView.addView(view,
@@ -212,11 +236,12 @@
                         ViewGroup.LayoutParams.MATCH_PARENT));
 
         mHandler = handler;
-        mMainDispatcher = mainDispatcher;
+        mBackgroundDispatcher = backgroundDispatcher;
         mMaxBurnInOffset = maxBurnInOffset;
         mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval;
         mMillisUntilFullJitter = millisUntilFullJitter;
         mPrimaryBouncerCallbackInteractor = primaryBouncerCallbackInteractor;
+        mDreamManager = dreamManager;
     }
 
     @Override
@@ -234,15 +259,26 @@
         mJitterStartTimeMillis = System.currentTimeMillis();
         mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval);
         mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback);
+        mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback);
         final Region emptyRegion = Region.obtain();
         mView.getRootSurfaceControl().setTouchableRegion(emptyRegion);
         emptyRegion.recycle();
 
-        collectFlow(
-                mView,
-                mKeyguardTransitionInteractor.isFinishedInStateWhere(KeyguardState::isBouncerState),
-                isFinished -> mAnyBouncerShowing = isFinished,
-                mMainDispatcher);
+        if (dreamHandlesBeingObscured()) {
+            collectFlow(
+                    mView,
+                    FlowKt.distinctUntilChanged(combineFlows(
+                            mKeyguardTransitionInteractor.isFinishedInStateWhere(
+                                    KeyguardState::isBouncerState),
+                            mShadeInteractor.isAnyExpanded(),
+                            mCommunalInteractor.isCommunalShowing(),
+                            (anyBouncerShowing, shadeExpanded, communalShowing) -> {
+                                mAnyBouncerShowing = anyBouncerShowing;
+                                return anyBouncerShowing || shadeExpanded || communalShowing;
+                            })),
+                    mDreamManager::setDreamIsObscured,
+                    mBackgroundDispatcher);
+        }
 
         // Start dream entry animations. Skip animations for low light clock.
         if (!mStateController.isLowLightActive()) {
@@ -255,8 +291,9 @@
 
     @Override
     protected void onViewDetached() {
-        mHandler.removeCallbacks(this::updateBurnInOffsets);
+        mHandler.removeCallbacksAndMessages(null);
         mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
+        mBouncerlessScrimController.removeCallback(mBouncerlessExpansionCallback);
 
         mDreamOverlayAnimationsController.cancelAnimations();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 999e681..789b7f8 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -18,6 +18,7 @@
 
 import android.content.res.Resources;
 import android.view.LayoutInflater;
+import android.view.View;
 import android.view.ViewGroup;
 
 import androidx.lifecycle.Lifecycle;
@@ -39,6 +40,7 @@
 @Module
 public abstract class DreamOverlayModule {
     public static final String DREAM_OVERLAY_CONTENT_VIEW = "dream_overlay_content_view";
+    public static final String HUB_GESTURE_INDICATOR_VIEW = "hub_gesture_indicator_view";
     public static final String MAX_BURN_IN_OFFSET = "max_burn_in_offset";
     public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL =
             "burn_in_protection_update_interval";
@@ -71,6 +73,18 @@
                 "R.id.dream_overlay_content must not be null");
     }
 
+    /**
+     * Gesture indicator bar on the right edge of the screen to indicate to users that they can
+     * swipe to see their widgets on lock screen.
+     */
+    @Provides
+    @DreamOverlayComponent.DreamOverlayScope
+    @Named(HUB_GESTURE_INDICATOR_VIEW)
+    public static View providesHubGestureIndicatorView(DreamOverlayContainerView view) {
+        return Preconditions.checkNotNull(view.findViewById(R.id.glanceable_hub_handle),
+                "R.id.glanceable_hub_handle must not be null");
+    }
+
     /** */
     @Provides
     public static TouchInsetManager.TouchInsetSession providesTouchInsetSession(
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
index 74452d1..2034138 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
@@ -21,7 +21,6 @@
 import android.content.ComponentName
 import android.os.PowerManager
 import android.os.UserHandle
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.common.domain.interactor.PackageChangeInteractor
 import com.android.systemui.common.shared.model.PackageChangeModel
 import com.android.systemui.controls.ControlsServiceInfo
@@ -36,6 +35,7 @@
 import com.android.systemui.util.kotlin.pairwiseBy
 import com.android.systemui.util.kotlin.sample
 import com.android.systemui.util.time.SystemClock
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import javax.inject.Inject
 import kotlin.math.abs
 import kotlin.time.Duration.Companion.milliseconds
@@ -132,7 +132,7 @@
                         ?: panels.firstOrNull()
                 item?.panelActivity
             }
-            .stateIn(bgScope, SharingStarted.WhileSubscribed(), null)
+            .stateIn(bgScope, SharingStarted.Eagerly, null)
 
     private val taskFragmentFinished =
         MutableSharedFlow<Long>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index fff0c58..1c047dd 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -98,7 +98,7 @@
         // Notification shade window has its own logic to be visible if the hub is open, no need to
         // do anything here other than send touch events over.
         session.registerInputListener(ev -> {
-            surfaces.handleExternalShadeWindowTouch((MotionEvent) ev);
+            surfaces.handleDreamTouch((MotionEvent) ev);
             if (ev != null && ((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
                 var unused = session.pop();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
index 221f790..c5b3c53 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
@@ -97,7 +98,7 @@
             .distinctUntilChanged()
 
     val transitionEnded =
-        keyguardTransitionInteractor.transition(from = DREAMING).filter { step ->
+        keyguardTransitionInteractor.transition(Edge.create(from = DREAMING)).filter { step ->
             step.transitionState == TransitionState.FINISHED ||
                 step.transitionState == TransitionState.CANCELED
         }
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
index 9876fe4..f04cbb8 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -477,7 +477,7 @@
         }
 
         private inline fun PrintWriter.wrapSection(entry: DumpsysEntry, block: () -> Unit) {
-            Trace.beginSection(entry.name)
+            Trace.beginSection(entry.name.take(Trace.MAX_SECTION_NAME_LEN))
             preamble(entry)
             val dumpTime = measureTimeMillis(block)
             footer(entry, dumpTime)
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 67c5564..1404340 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -29,11 +29,13 @@
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.shared.ComposeLockscreen
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor
 import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
+import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
 import javax.inject.Inject
 
 /** A class in which engineers can define flag dependencies */
@@ -49,6 +51,7 @@
         NotificationsLiveDataStoreRefactor.token dependsOn NotificationIconContainerRefactor.token
         FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token
         NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token
+        PriorityPeopleSection.token dependsOn SortBySectionTimeFlag.token
 
         // SceneContainer dependencies
         SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 95bc514..49be03c 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -2739,12 +2739,10 @@
         protected final void setRotationSuggestionsEnabled(boolean enabled) {
             try {
                 final int userId = Binder.getCallingUserHandle().getIdentifier();
-                StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
-                if (enabled) {
-                    info.setRotationSuggestionDisabled(true);
-                }
-                mStatusBarService.disableForUser(info, mToken, mContext.getPackageName(), userId,
-                        "setRotationSuggestionsEnabled");
+                final int what = enabled
+                        ? StatusBarManager.DISABLE2_NONE
+                        : StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS;
+                mStatusBarService.disable2ForUser(what, mToken, mContext.getPackageName(), userId);
             } catch (RemoteException ex) {
                 throw ex.rethrowFromSystemServer();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt
index c6fb4f9..fc9406b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt
@@ -19,12 +19,13 @@
 
 import com.android.systemui.keyboard.data.repository.KeyboardRepository
 import com.android.systemui.keyboard.data.repository.KeyboardRepositoryImpl
+import com.android.systemui.keyboard.shortcut.ShortcutHelperModule
 import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepository
 import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepositoryImpl
 import dagger.Binds
 import dagger.Module
 
-@Module
+@Module(includes = [ShortcutHelperModule::class])
 abstract class KeyboardModule {
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt
new file mode 100644
index 0000000..5635f80
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.keyboard.shortcut
+
+import android.app.Activity
+import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.keyboardShortcutHelperRewrite
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperRepository
+import com.android.systemui.keyboard.shortcut.ui.ShortcutHelperActivityStarter
+import com.android.systemui.keyboard.shortcut.ui.view.ShortcutHelperActivity
+import dagger.Binds
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface ShortcutHelperModule {
+
+    @Binds
+    @IntoMap
+    @ClassKey(ShortcutHelperActivity::class)
+    fun activity(impl: ShortcutHelperActivity): Activity
+
+    companion object {
+        @Provides
+        @IntoMap
+        @ClassKey(ShortcutHelperActivityStarter::class)
+        fun starter(implLazy: Lazy<ShortcutHelperActivityStarter>): CoreStartable {
+            return if (keyboardShortcutHelperRewrite()) {
+                implLazy.get()
+            } else {
+                // No-op implementation when the flag is disabled.
+                NoOpStartable
+            }
+        }
+
+        @Provides
+        @IntoMap
+        @ClassKey(ShortcutHelperRepository::class)
+        fun repo(implLazy: Lazy<ShortcutHelperRepository>): CoreStartable {
+            return if (keyboardShortcutHelperRewrite()) {
+                implLazy.get()
+            } else {
+                // No-op implementation when the flag is disabled.
+                NoOpStartable
+            }
+        }
+    }
+}
+
+private object NoOpStartable : CoreStartable {
+    override fun start() {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperRepository.kt
new file mode 100644
index 0000000..9450af4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperRepository.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.keyboard.shortcut.data.repository
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import com.android.systemui.CoreStartable
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Inactive
+import com.android.systemui.statusbar.CommandQueue
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+@SysUISingleton
+class ShortcutHelperRepository
+@Inject
+constructor(
+    private val commandQueue: CommandQueue,
+    private val broadcastDispatcher: BroadcastDispatcher,
+) : CoreStartable {
+
+    val state = MutableStateFlow<ShortcutHelperState>(Inactive)
+
+    override fun start() {
+        registerBroadcastReceiver(
+            action = Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS,
+            onReceive = { state.value = Active() }
+        )
+        registerBroadcastReceiver(
+            action = Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS,
+            onReceive = { state.value = Inactive }
+        )
+        commandQueue.addCallback(
+            object : CommandQueue.Callbacks {
+                override fun dismissKeyboardShortcutsMenu() {
+                    state.value = Inactive
+                }
+
+                override fun toggleKeyboardShortcutsMenu(deviceId: Int) {
+                    state.value =
+                        if (state.value is Inactive) {
+                            Active(deviceId)
+                        } else {
+                            Inactive
+                        }
+                }
+            }
+        )
+    }
+
+    fun hide() {
+        state.value = Inactive
+    }
+
+    private fun registerBroadcastReceiver(action: String, onReceive: () -> Unit) {
+        broadcastDispatcher.registerReceiver(
+            receiver =
+                object : BroadcastReceiver() {
+                    override fun onReceive(context: Context, intent: Intent) {
+                        onReceive()
+                    }
+                },
+            filter = IntentFilter(action),
+            flags = Context.RECEIVER_EXPORTED or Context.RECEIVER_VISIBLE_TO_INSTANT_APPS
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperInteractor.kt
new file mode 100644
index 0000000..44f1c1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperInteractor.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.keyboard.shortcut.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperRepository
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState
+import com.android.systemui.model.SysUiState
+import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.shared.system.QuickStepContract
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class ShortcutHelperInteractor
+@Inject
+constructor(
+    private val displayTracker: DisplayTracker,
+    @Background private val backgroundScope: CoroutineScope,
+    private val sysUiState: SysUiState,
+    private val repository: ShortcutHelperRepository
+) {
+
+    val state: Flow<ShortcutHelperState> = repository.state
+
+    fun onViewClosed() {
+        repository.hide()
+        setSysUiStateFlagEnabled(false)
+    }
+
+    fun onViewOpened() {
+        setSysUiStateFlagEnabled(true)
+    }
+
+    private fun setSysUiStateFlagEnabled(enabled: Boolean) {
+        backgroundScope.launch {
+            sysUiState
+                .setFlag(QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING, enabled)
+                .commitUpdate(displayTracker.defaultDisplayId)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutHelperState.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutHelperState.kt
index bf22563..d22d6c8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutHelperState.kt
@@ -14,12 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.keyboard.shortcut.shared.model
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+sealed interface ShortcutHelperState {
+    data object Inactive : ShortcutHelperState
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
+    data class Active(val deviceId: Int? = null) : ShortcutHelperState
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarter.kt
new file mode 100644
index 0000000..fbf52e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarter.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.keyboard.shortcut.ui
+
+import android.content.Context
+import android.content.Intent
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyboard.shortcut.ui.view.ShortcutHelperActivity
+import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class ShortcutHelperActivityStarter(
+    private val context: Context,
+    @Application private val applicationScope: CoroutineScope,
+    private val viewModel: ShortcutHelperViewModel,
+    private val startActivity: (Intent) -> Unit,
+) : CoreStartable {
+
+    @Inject
+    constructor(
+        context: Context,
+        @Application applicationScope: CoroutineScope,
+        viewModel: ShortcutHelperViewModel,
+    ) : this(
+        context,
+        applicationScope,
+        viewModel,
+        startActivity = { intent -> context.startActivity(intent) }
+    )
+
+    override fun start() {
+        applicationScope.launch {
+            viewModel.shouldShow.collect { shouldShow ->
+                if (shouldShow) {
+                    startShortcutHelperActivity()
+                }
+            }
+        }
+    }
+
+    private fun startShortcutHelperActivity() {
+        startActivity(
+            Intent(context, ShortcutHelperActivity::class.java)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperActivity.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
similarity index 85%
rename from packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperActivity.kt
rename to packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
index 692fbb0..ef4156d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyboard.shortcut
+package com.android.systemui.keyboard.shortcut.ui.view
 
 import android.graphics.Insets
 import android.os.Bundle
@@ -24,16 +24,25 @@
 import androidx.activity.ComponentActivity
 import androidx.activity.OnBackPressedCallback
 import androidx.core.view.updatePadding
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel
 import com.android.systemui.res.R
 import com.google.android.material.bottomsheet.BottomSheetBehavior
 import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
 import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
+import javax.inject.Inject
+import kotlinx.coroutines.launch
 
 /**
  * Activity that hosts the new version of the keyboard shortcut helper. It will be used both for
  * small and large screen devices.
  */
-class ShortcutHelperActivity : ComponentActivity() {
+class ShortcutHelperActivity
+@Inject
+constructor(
+    private val viewModel: ShortcutHelperViewModel,
+) : ComponentActivity() {
 
     private val bottomSheetContainer
         get() = requireViewById<View>(R.id.shortcut_helper_sheet_container)
@@ -53,6 +62,25 @@
         setUpPredictiveBack()
         setUpSheetDismissListener()
         setUpDismissOnTouchOutside()
+        observeFinishRequired()
+        viewModel.onViewOpened()
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        if (isFinishing) {
+            viewModel.onViewClosed()
+        }
+    }
+
+    private fun observeFinishRequired() {
+        lifecycleScope.launch {
+            viewModel.shouldShow.flowWithLifecycle(lifecycle).collect { shouldShow ->
+                if (!shouldShow) {
+                    finish()
+                }
+            }
+        }
     }
 
     private fun setupEdgeToEdge() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
new file mode 100644
index 0000000..c623f5c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.keyboard.shortcut.ui.viewmodel
+
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperInteractor
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+class ShortcutHelperViewModel
+@Inject
+constructor(
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val interactor: ShortcutHelperInteractor
+) {
+
+    val shouldShow =
+        interactor.state
+            .map { it is ShortcutHelperState.Active }
+            .distinctUntilChanged()
+            .flowOn(backgroundDispatcher)
+
+    fun onViewClosed() {
+        interactor.onViewClosed()
+    }
+
+    fun onViewOpened() {
+        interactor.onViewOpened()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index dbaa297..68a252b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -37,6 +37,7 @@
 import android.service.notification.ZenModeConfig;
 import android.text.TextUtils;
 import android.text.style.StyleSpan;
+import android.util.Log;
 
 import androidx.core.graphics.drawable.IconCompat;
 import androidx.slice.Slice;
@@ -212,21 +213,27 @@
     @AnyThread
     @Override
     public Slice onBindSlice(Uri sliceUri) {
-        Trace.beginSection("KeyguardSliceProvider#onBindSlice");
-        Slice slice;
-        synchronized (this) {
-            ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY);
-            if (needsMediaLocked()) {
-                addMediaLocked(builder);
-            } else {
-                builder.addRow(new RowBuilder(mDateUri).setTitle(mLastText));
+        Slice slice = null;
+        try {
+            Trace.beginSection("KeyguardSliceProvider#onBindSlice");
+            synchronized (this) {
+                ListBuilder builder = new ListBuilder(getContext(), mSliceUri,
+                        ListBuilder.INFINITY);
+                if (needsMediaLocked()) {
+                    addMediaLocked(builder);
+                } else {
+                    builder.addRow(new RowBuilder(mDateUri).setTitle(mLastText));
+                }
+                addNextAlarmLocked(builder);
+                addZenModeLocked(builder);
+                addPrimaryActionLocked(builder);
+                slice = builder.build();
             }
-            addNextAlarmLocked(builder);
-            addZenModeLocked(builder);
-            addPrimaryActionLocked(builder);
-            slice = builder.build();
+        } catch (IllegalStateException e) {
+            Log.w(TAG, "Could not initialize slice", e);
+        } finally {
+            Trace.endSection();
         }
-        Trace.endSection();
         return slice;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index d6fd354..81c2d92 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.keyguard;
 
-import static android.app.StatusBarManager.DISABLE2_NONE;
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
 import static android.provider.Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT;
 import static android.provider.Settings.System.LOCKSCREEN_SOUNDS_ENABLED;
@@ -1077,6 +1076,33 @@
         }
     };
 
+    /**
+     * For now, the keyguard-appearing animation is a no-op, because we assume that this is
+     * happening while the screen is already off or turning off.
+     *
+     * TODO(b/278086361): create an animation for keyguard appearing over a non-showWhenLocked
+     * activity.
+     */
+    private final IRemoteAnimationRunner.Stub mAppearAnimationRunner =
+            new IRemoteAnimationRunner.Stub() {
+        @Override
+        public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+                RemoteAnimationTarget[] apps,
+                RemoteAnimationTarget[] wallpapers,
+                RemoteAnimationTarget[] nonApps,
+                IRemoteAnimationFinishedCallback finishedCallback) {
+            try {
+                finishedCallback.onAnimationFinished();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to finish transition", e);
+            }
+        }
+
+        @Override
+        public void onAnimationCancelled() {
+        }
+    };
+
     private final IRemoteAnimationRunner mOccludeAnimationRunner =
             new OccludeActivityLaunchRemoteAnimationRunner(mOccludeAnimationController);
 
@@ -1165,7 +1191,7 @@
                                     finishedCallback.onAnimationFinished();
                                     mOccludeByDreamAnimator = null;
                                 } catch (RemoteException e) {
-                                    e.printStackTrace();
+                                    Log.e(TAG, "Failed to finish transition", e);
                                 }
                             }
                         });
@@ -1280,7 +1306,7 @@
 
                                     mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION);
                                 } catch (RemoteException e) {
-                                    e.printStackTrace();
+                                    Log.e(TAG, "Failed to finish transition", e);
                                 }
                             }
                         });
@@ -1546,6 +1572,7 @@
 
         mKeyguardTransitions.register(
                 KeyguardService.wrap(this, getExitAnimationRunner()),
+                KeyguardService.wrap(this, getAppearAnimationRunner()),
                 KeyguardService.wrap(this, getOccludeAnimationRunner()),
                 KeyguardService.wrap(this, getOccludeByDreamAnimationRunner()),
                 KeyguardService.wrap(this, getUnoccludeAnimationRunner()));
@@ -2124,6 +2151,10 @@
         return validatingRemoteAnimationRunner(mExitAnimationRunner);
     }
 
+    public IRemoteAnimationRunner getAppearAnimationRunner() {
+        return validatingRemoteAnimationRunner(mAppearAnimationRunner);
+    }
+
     public IRemoteAnimationRunner getOccludeAnimationRunner() {
         if (KeyguardWmStateRefactor.isEnabled()) {
             return validatingRemoteAnimationRunner(mWmOcclusionManager.getOccludeAnimationRunner());
@@ -3357,7 +3388,7 @@
             }
         } catch (RemoteException e) {
             mSurfaceBehindRemoteAnimationRequested = false;
-            e.printStackTrace();
+            Log.e(TAG, "Failed to report keyguardGoingAway", e);
         }
     }
 
@@ -3440,12 +3471,9 @@
             //  unless disable is called to show un-hide it once first
             if (forceClearFlags) {
                 try {
-                    StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(flags,
-                            DISABLE2_NONE);
-                    mStatusBarService.disableForUser(info, mStatusBarDisableToken,
+                    mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
                             mContext.getPackageName(),
-                            mSelectedUserInteractor.getSelectedUserId(true),
-                            "adjustStatusBarLocked - force clear flags");
+                            mSelectedUserInteractor.getSelectedUserId(true));
                 } catch (RemoteException e) {
                     Log.d(TAG, "Failed to force clear flags", e);
                 }
@@ -3471,11 +3499,9 @@
             }
 
             try {
-                StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(flags,
-                        DISABLE2_NONE);
-                mStatusBarService.disableForUser(info, mStatusBarDisableToken,
-                        mContext.getPackageName(), mSelectedUserInteractor.getSelectedUserId(true),
-                        "adjustStatusBarLocked - set disable flags");
+                mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
+                        mContext.getPackageName(),
+                        mSelectedUserInteractor.getSelectedUserId(true));
             } catch (RemoteException e) {
                 Log.d(TAG, "Failed to set disable flags: " + flags, e);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
index a65a882..3cbcb2c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
@@ -29,15 +29,20 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.utils.GlobalWindowManager
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
 
@@ -59,6 +64,7 @@
     @Application private val applicationScope: CoroutineScope,
     @Background private val bgDispatcher: CoroutineDispatcher,
     private val featureFlags: FeatureFlags,
+    private val sceneInteractor: SceneInteractor,
 ) : CoreStartable, WakefulnessLifecycle.Observer {
 
     override fun start() {
@@ -84,9 +90,15 @@
 
         applicationScope.launch(bgDispatcher) {
             // We drop 1 to avoid triggering on initial collect().
-            keyguardTransitionInteractor.transition(to = GONE).collect { transition ->
-                if (transition.transitionState == TransitionState.FINISHED) {
-                    onKeyguardGone()
+            if (SceneContainerFlag.isEnabled) {
+                sceneInteractor.transitionState
+                    .filter { it.isIdle(Scenes.Gone) }
+                    .collect { onKeyguardGone() }
+            } else {
+                keyguardTransitionInteractor.transition(Edge.create(to = GONE)).collect {
+                    if (it.transitionState == TransitionState.FINISHED) {
+                        onKeyguardGone()
+                    }
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
index 00f5002..1b342ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -23,6 +23,7 @@
 import android.view.WindowManager
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import java.util.concurrent.Executor
@@ -40,6 +41,7 @@
     private val activityTaskManagerService: IActivityTaskManager,
     private val keyguardStateController: KeyguardStateController,
     private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
 ) {
 
     /**
@@ -141,6 +143,14 @@
         finishedCallback: IRemoteAnimationFinishedCallback
     ) {
         if (apps.isNotEmpty()) {
+            // Ensure that we've started a dismiss keyguard transition. WindowManager can start the
+            // going away animation on its own, if an activity launches and then requests dismissing
+            // the keyguard. In this case, this is the first and only signal we'll receive to start
+            // a transition to GONE.
+            keyguardTransitionInteractor.startDismissKeyguardTransition(
+                reason = "Going away remote animation started"
+            )
+
             goingAwayRemoteAnimationFinishedCallback = finishedCallback
             keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0])
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
index 4a726ae..c11c49c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
@@ -19,6 +19,7 @@
 
 import android.os.Handler
 import android.util.Log
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
@@ -57,7 +58,7 @@
         TreeMap<String, KeyguardBlueprint>().apply { putAll(blueprints.associateBy { it.id }) }
     val blueprint: MutableStateFlow<KeyguardBlueprint> = MutableStateFlow(blueprintIdMap[DEFAULT]!!)
     val refreshTransition = MutableSharedFlow<Config>(extraBufferCapacity = 1)
-    private var targetTransitionConfig: Config? = null
+    @VisibleForTesting var targetTransitionConfig: Config? = null
 
     /**
      * Emits the blueprint value to the collectors.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
index 956125c..a1e4af5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
@@ -51,10 +51,6 @@
 ) : KeyguardSmartspaceRepository {
     private val _bcSmartspaceVisibility: MutableStateFlow<Int> = MutableStateFlow(View.GONE)
     override val bcSmartspaceVisibility: StateFlow<Int> = _bcSmartspaceVisibility.asStateFlow()
-    val defaultValue =
-        context.resources.getBoolean(
-            com.android.internal.R.bool.config_lockscreenWeatherEnabledByDefault
-        )
     override val isWeatherEnabled: StateFlow<Boolean> =
         secureSettings
             .observerFlow(
@@ -76,7 +72,7 @@
     private fun getLockscreenWeatherEnabled(): Boolean {
         return secureSettings.getIntForUser(
             Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED,
-            if (defaultValue) 1 else 0,
+            1,
             userTracker.userId
         ) == 1
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 4c54bfd..f488d3b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -34,6 +34,7 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -42,6 +43,7 @@
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.sync.Mutex
 
 /**
  * The source of truth for all keyguard transitions.
@@ -89,6 +91,12 @@
     suspend fun startTransition(info: TransitionInfo): UUID?
 
     /**
+     * Emits STARTED and FINISHED transition steps to the given state. This is used during boot to
+     * seed the repository with the appropriate initial state.
+     */
+    suspend fun emitInitialStepsFromOff(to: KeyguardState)
+
+    /**
      * Allows manual control of a transition. When calling [startTransition], the consumer must pass
      * in a null animator. In return, it will get a unique [UUID] that will be validated to allow
      * further updates.
@@ -123,12 +131,13 @@
     private var lastStep: TransitionStep = TransitionStep()
     private var lastAnimator: ValueAnimator? = null
 
+    private val _currentTransitionMutex = Mutex()
     private val _currentTransitionInfo: MutableStateFlow<TransitionInfo> =
         MutableStateFlow(
             TransitionInfo(
                 ownerName = "",
                 from = KeyguardState.OFF,
-                to = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.OFF,
                 animator = null
             )
         )
@@ -140,17 +149,40 @@
      */
     private var updateTransitionId: UUID? = null
 
+    // Only used in a test environment
+    var forceDelayForRaceConditionTest = false
+
     init {
-        // Seed with transitions signaling a boot into lockscreen state. If updating this, please
-        // also update FakeKeyguardTransitionRepository.
-        initialTransitionSteps.forEach(::emitTransition)
+        // Start with a FINISHED transition in OFF. KeyguardBootInteractor will transition from OFF
+        // to either GONE or LOCKSCREEN once we're booted up and can determine which state we should
+        // start in.
+        emitTransition(
+            TransitionStep(
+                KeyguardState.OFF,
+                KeyguardState.OFF,
+                1f,
+                TransitionState.FINISHED,
+            )
+        )
     }
 
     override suspend fun startTransition(info: TransitionInfo): UUID? {
         _currentTransitionInfo.value = info
+        Log.d(TAG, "(Internal) Setting current transition info: $info")
+
+        // There is no fairness guarantee with 'withContext', which means that transitions could
+        // be processed out of order. Use a Mutex to guarantee ordering.
+        _currentTransitionMutex.lock()
+
+        // Only used in a test environment
+        if (forceDelayForRaceConditionTest) {
+            delay(50L)
+        }
 
         // Animators must be started on the main thread.
         return withContext("$TAG#startTransition", mainDispatcher) {
+            _currentTransitionMutex.unlock()
+
             if (lastStep.from == info.from && lastStep.to == info.to) {
                 Log.i(TAG, "Duplicate call to start the transition, rejecting: $info")
                 return@withContext null
@@ -233,7 +265,7 @@
         state: TransitionState
     ) {
         if (updateTransitionId != transitionId) {
-            Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
+            Log.w(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
             return
         }
 
@@ -251,6 +283,36 @@
         lastStep = nextStep
     }
 
+    override suspend fun emitInitialStepsFromOff(to: KeyguardState) {
+        _currentTransitionInfo.value =
+            TransitionInfo(
+                ownerName = "KeyguardTransitionRepository(boot)",
+                from = KeyguardState.OFF,
+                to = to,
+                animator = null
+            )
+
+        emitTransition(
+            TransitionStep(
+                KeyguardState.OFF,
+                to,
+                0f,
+                TransitionState.STARTED,
+                ownerName = "KeyguardTransitionRepository(boot)",
+            )
+        )
+
+        emitTransition(
+            TransitionStep(
+                KeyguardState.OFF,
+                to,
+                1f,
+                TransitionState.FINISHED,
+                ownerName = "KeyguardTransitionRepository(boot)",
+            ),
+        )
+    }
+
     private fun logAndTrace(step: TransitionStep, isManual: Boolean) {
         if (step.transitionState == TransitionState.RUNNING) {
             return
@@ -271,31 +333,5 @@
 
     companion object {
         private const val TAG = "KeyguardTransitionRepository"
-
-        /**
-         * Transition steps to seed the repository with, so that all of the transition interactor
-         * flows emit reasonable initial values.
-         */
-        val initialTransitionSteps: List<TransitionStep> =
-            listOf(
-                TransitionStep(
-                    KeyguardState.OFF,
-                    KeyguardState.OFF,
-                    1f,
-                    TransitionState.FINISHED,
-                ),
-                TransitionStep(
-                    KeyguardState.OFF,
-                    KeyguardState.LOCKSCREEN,
-                    0f,
-                    TransitionState.STARTED,
-                ),
-                TransitionStep(
-                    KeyguardState.OFF,
-                    KeyguardState.LOCKSCREEN,
-                    1f,
-                    TransitionState.FINISHED,
-                ),
-            )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt
new file mode 100644
index 0000000..80bdc65
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+@SysUISingleton
+class LockscreenSceneTransitionRepository @Inject constructor() {
+
+    /**
+     * This [KeyguardState] will indicate which sub state within KTF should be navigated to when the
+     * next transition into the Lockscreen scene is started. It will be consumed exactly once and
+     * after that the state will be set back to [DEFAULT_STATE].
+     */
+    val nextLockscreenTargetState: MutableStateFlow<KeyguardState> = MutableStateFlow(DEFAULT_STATE)
+
+    companion object {
+        val DEFAULT_STATE = KeyguardState.LOCKSCREEN
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index eef4b97..9626077 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.content.Context
+import android.util.Log
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
@@ -39,6 +40,7 @@
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 
 /**
@@ -96,10 +98,13 @@
                     keyguardUpdateMonitor.isFingerprintDetectionRunning &&
                     keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed
             }
+            .onEach { Log.d(TAG, "showIndicatorForPrimaryBouncer updated: $it") }
 
     private val showIndicatorForAlternateBouncer: Flow<Boolean> =
         // Note: this interactor internally verifies that SideFPS is enabled and running.
-        alternateBouncerInteractor.isVisible
+        alternateBouncerInteractor.isVisible.onEach {
+            Log.d(TAG, "showIndicatorForAlternateBouncer updated: $it")
+        }
 
     /**
      * Indicates whether the primary or alternate bouncers request showing the side fingerprint
@@ -112,6 +117,7 @@
                 showForPrimaryBouncer || showForAlternateBouncer
             }
             .distinctUntilChanged()
+            .onEach { Log.d(TAG, "showIndicatorForDeviceEntry updated: $it") }
 
     private fun isBouncerActive(): Boolean {
         if (SceneContainerFlag.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 5a28f711..9b07675f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
 import com.android.wm.shell.animation.Interpolators
 import javax.inject.Inject
@@ -140,6 +141,8 @@
     }
 
     private fun listenForAlternateBouncerToGone() {
+        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        if (SceneContainerFlag.isEnabled) return
         if (KeyguardWmStateRefactor.isEnabled) {
             // Handled via #dismissAlternateBouncer.
             return
@@ -162,6 +165,8 @@
     }
 
     private fun listenForAlternateBouncerToPrimaryBouncer() {
+        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        if (SceneContainerFlag.isEnabled) return
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
                 .filterRelevantKeyguardStateAnd { isPrimaryBouncerShowing ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 4d73774..a306954 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.Utils.Companion.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -185,6 +186,7 @@
      * PRIMARY_BOUNCER.
      */
     private fun listenForAodToPrimaryBouncer() {
+        if (SceneContainerFlag.isEnabled) return
         scope.launch("$TAG#listenForAodToPrimaryBouncer") {
             keyguardInteractor.primaryBouncerShowing
                 .filterRelevantKeyguardStateAnd { primaryBouncerShowing -> primaryBouncerShowing }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 2eeb3b9..115fc36 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -66,7 +66,7 @@
         listenForTransitionToCamera(scope, keyguardInteractor)
     }
 
-    private val canDismissLockScreen: Flow<Boolean> =
+    private val canTransitionToGoneOnWake: Flow<Boolean> =
         combine(
             keyguardInteractor.isKeyguardShowing,
             keyguardInteractor.isKeyguardDismissible,
@@ -87,7 +87,7 @@
                     keyguardInteractor.biometricUnlockState,
                     keyguardInteractor.isKeyguardOccluded,
                     communalInteractor.isIdleOnCommunal,
-                    canDismissLockScreen,
+                    canTransitionToGoneOnWake,
                     keyguardInteractor.primaryBouncerShowing,
                 )
                 .collect {
@@ -96,12 +96,12 @@
                         biometricUnlockState,
                         occluded,
                         isIdleOnCommunal,
-                        canDismissLockScreen,
+                        canTransitionToGoneOnWake,
                         primaryBouncerShowing) ->
                     startTransitionTo(
                         if (isWakeAndUnlock(biometricUnlockState.mode)) {
                             KeyguardState.GONE
-                        } else if (canDismissLockScreen) {
+                        } else if (canTransitionToGoneOnWake) {
                             KeyguardState.GONE
                         } else if (primaryBouncerShowing) {
                             KeyguardState.PRIMARY_BOUNCER
@@ -129,7 +129,7 @@
                 .sample(
                     communalInteractor.isIdleOnCommunal,
                     keyguardInteractor.biometricUnlockState,
-                    canDismissLockScreen,
+                    canTransitionToGoneOnWake,
                     keyguardInteractor.primaryBouncerShowing,
                 )
                 .collect {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
index e738ea4..63294f7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -93,6 +94,8 @@
     }
 
     private fun listenForDreamingLockscreenHostedToPrimaryBouncer() {
+        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        if (SceneContainerFlag.isEnabled) return
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
                 .filterRelevantKeyguardStateAnd { isBouncerShowing -> isBouncerShowing }
@@ -101,6 +104,8 @@
     }
 
     private fun listenForDreamingLockscreenHostedToGone() {
+        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        if (SceneContainerFlag.isEnabled) return
         scope.launch {
             keyguardInteractor.biometricUnlockState
                 .filterRelevantKeyguardStateAnd { biometricUnlockState ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index c952e08..7961b45 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
@@ -88,6 +89,8 @@
 
     private fun listenForDreamingToGlanceableHub() {
         if (!communalHub()) return
+        if (SceneContainerFlag.isEnabled)
+            return // TODO(b/336576536): Check if adaptation for scene framework is needed
         scope.launch("$TAG#listenForDreamingToGlanceableHub", mainDispatcher) {
             glanceableHubTransitions.listenForGlanceableHubTransition(
                 transitionOwnerName = TAG,
@@ -175,6 +178,8 @@
     }
 
     private fun listenForDreamingToGoneWhenDismissable() {
+        if (SceneContainerFlag.isEnabled)
+            return // TODO(b/336576536): Check if adaptation for scene framework is needed
         scope.launch {
             keyguardInteractor.isAbleToDream
                 .sampleCombine(
@@ -190,6 +195,8 @@
     }
 
     private fun listenForDreamingToGoneFromBiometricUnlock() {
+        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        if (SceneContainerFlag.isEnabled) return
         scope.launch {
             keyguardInteractor.biometricUnlockState
                 .filterRelevantKeyguardStateAnd { biometricUnlockState ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 54d9a78..ca6ab3e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -28,9 +28,11 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.util.kotlin.BooleanFlowOperators.and
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -62,6 +64,8 @@
     ) {
 
     override fun start() {
+        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        if (SceneContainerFlag.isEnabled) return
         if (!Flags.communalHub()) {
             return
         }
@@ -79,6 +83,7 @@
             duration =
                 when (toState) {
                     KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+                    KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
                     else -> DEFAULT_DURATION
                 }.inWholeMilliseconds
         }
@@ -148,7 +153,7 @@
             }
         } else {
             scope.launch {
-                and(keyguardInteractor.isKeyguardOccluded, not(keyguardInteractor.isDreaming))
+                allOf(keyguardInteractor.isKeyguardOccluded, not(keyguardInteractor.isDreaming))
                     .filterRelevantKeyguardStateAnd { isOccludedAndNotDreaming ->
                         isOccludedAndNotDreaming
                     }
@@ -171,5 +176,6 @@
         const val TAG = "FromGlanceableHubTransitionInteractor"
         val DEFAULT_DURATION = 1.seconds
         val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION
+        val TO_OCCLUDED_DURATION = 450.milliseconds
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index c2c095b..2b3732f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -62,6 +63,8 @@
     ) {
 
     override fun start() {
+        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        if (SceneContainerFlag.isEnabled) return
         listenForGoneToAodOrDozing()
         listenForGoneToDreaming()
         listenForGoneToLockscreenOrHub()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 56261e0..f1e98f3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -20,6 +20,7 @@
 import android.util.MathUtils
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.launch
+import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -32,6 +33,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
 import java.util.UUID
@@ -150,6 +152,7 @@
     }
 
     private fun listenForLockscreenToPrimaryBouncer() {
+        if (SceneContainerFlag.isEnabled) return
         scope.launch("$TAG#listenForLockscreenToPrimaryBouncer") {
             keyguardInteractor.primaryBouncerShowing
                 .filterRelevantKeyguardStateAnd { isBouncerShowing -> isBouncerShowing }
@@ -174,6 +177,7 @@
 
     /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
     private fun listenForLockscreenToPrimaryBouncerDragging() {
+        if (SceneContainerFlag.isEnabled) return
         var transitionId: UUID? = null
         scope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") {
             shadeRepository.legacyShadeExpansion
@@ -259,7 +263,9 @@
     }
 
     fun dismissKeyguard() {
-        scope.launch("$TAG#dismissKeyguard") { startTransitionTo(KeyguardState.GONE) }
+        scope.launch("$TAG#dismissKeyguard") {
+            startTransitionTo(KeyguardState.GONE, ownerReason = "#dismissKeyguard()")
+        }
     }
 
     private fun listenForLockscreenToGone() {
@@ -280,6 +286,7 @@
     }
 
     private fun listenForLockscreenToGoneDragging() {
+        if (SceneContainerFlag.isEnabled) return
         if (KeyguardWmStateRefactor.isEnabled) {
             // When the refactor is enabled, we no longer use isKeyguardGoingAway.
             scope.launch("$TAG#listenForLockscreenToGoneDragging") {
@@ -337,7 +344,9 @@
      * keyguard transition.
      */
     private fun listenForLockscreenToGlanceableHub() {
-        if (!com.android.systemui.Flags.communalHub()) {
+        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        if (SceneContainerFlag.isEnabled) return
+        if (!Flags.communalHub()) {
             return
         }
         scope.launch(mainDispatcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index e51ba83..2603aab2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -18,6 +18,7 @@
 
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
+import com.android.systemui.Flags.restartDreamOnUnocclude
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -26,6 +27,7 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.Utils.Companion.sample
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
@@ -90,17 +92,14 @@
                     .sample(
                         communalInteractor.isIdleOnCommunal,
                         communalInteractor.showCommunalFromOccluded,
+                        communalInteractor.dreamFromOccluded,
                     )
-                    .collect { (_, isIdleOnCommunal, showCommunalFromOccluded) ->
-                        // Occlusion signals come from the framework, and should interrupt any
-                        // existing transition
-                        val to =
-                            if (isIdleOnCommunal || showCommunalFromOccluded) {
-                                KeyguardState.GLANCEABLE_HUB
-                            } else {
-                                KeyguardState.LOCKSCREEN
-                            }
-                        startTransitionTo(to)
+                    .collect { (_, isIdleOnCommunal, showCommunalFromOccluded, dreamFromOccluded) ->
+                        startTransitionToLockscreenOrHub(
+                            isIdleOnCommunal,
+                            showCommunalFromOccluded,
+                            dreamFromOccluded
+                        )
                     }
             }
         } else {
@@ -110,26 +109,42 @@
                         keyguardInteractor.isKeyguardShowing,
                         communalInteractor.isIdleOnCommunal,
                         communalInteractor.showCommunalFromOccluded,
+                        communalInteractor.dreamFromOccluded,
                     )
-                    .filterRelevantKeyguardStateAnd { (isOccluded, isShowing, _, _) ->
+                    .filterRelevantKeyguardStateAnd { (isOccluded, isShowing, _, _, _) ->
                         !isOccluded && isShowing
                     }
-                    .collect { (_, _, isIdleOnCommunal, showCommunalFromOccluded) ->
-                        // Occlusion signals come from the framework, and should interrupt any
-                        // existing transition
-                        val to =
-                            if (isIdleOnCommunal || showCommunalFromOccluded) {
-                                KeyguardState.GLANCEABLE_HUB
-                            } else {
-                                KeyguardState.LOCKSCREEN
-                            }
-                        startTransitionTo(to)
+                    .collect { (_, _, isIdleOnCommunal, showCommunalFromOccluded, dreamFromOccluded)
+                        ->
+                        startTransitionToLockscreenOrHub(
+                            isIdleOnCommunal,
+                            showCommunalFromOccluded,
+                            dreamFromOccluded
+                        )
                     }
             }
         }
     }
 
+    private suspend fun FromOccludedTransitionInteractor.startTransitionToLockscreenOrHub(
+        isIdleOnCommunal: Boolean,
+        showCommunalFromOccluded: Boolean,
+        dreamFromOccluded: Boolean,
+    ) {
+        if (restartDreamOnUnocclude() && dreamFromOccluded) {
+            startTransitionTo(KeyguardState.DREAMING)
+        } else if (isIdleOnCommunal || showCommunalFromOccluded) {
+            // TODO(b/336576536): Check if adaptation for scene framework is needed
+            if (SceneContainerFlag.isEnabled) return
+            startTransitionTo(KeyguardState.GLANCEABLE_HUB)
+        } else {
+            startTransitionTo(KeyguardState.LOCKSCREEN)
+        }
+    }
+
     private fun listenForOccludedToGone() {
+        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        if (SceneContainerFlag.isEnabled) return
         if (KeyguardWmStateRefactor.isEnabled) {
             // We don't think OCCLUDED to GONE is possible. You should always have to go via a
             // *_BOUNCER state to end up GONE. Launching an activity over a dismissable keyguard
@@ -150,10 +165,6 @@
         }
     }
 
-    fun dismissToGone() {
-        scope.launch { startTransitionTo(KeyguardState.GONE) }
-    }
-
     private fun listenForOccludedToAsleep() {
         scope.launch { listenForSleepTransition() }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 181a551..53a0c32 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.sample
 import com.android.systemui.util.kotlin.sample
@@ -98,6 +99,8 @@
     }
 
     private fun listenForPrimaryBouncerToLockscreenHubOrOccluded() {
+        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        if (SceneContainerFlag.isEnabled) return
         if (KeyguardWmStateRefactor.isEnabled) {
             scope.launch {
                 keyguardInteractor.primaryBouncerShowing
@@ -158,10 +161,14 @@
     }
 
     private fun listenForPrimaryBouncerToAsleep() {
+        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        if (SceneContainerFlag.isEnabled) return
         scope.launch { listenForSleepTransition() }
     }
 
     private fun listenForPrimaryBouncerToDreamingLockscreenHosted() {
+        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        if (SceneContainerFlag.isEnabled) return
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
                 .sample(keyguardInteractor.isActiveDreamLockscreenHosted, ::Pair)
@@ -174,6 +181,8 @@
     }
 
     private fun listenForPrimaryBouncerToGone() {
+        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        if (SceneContainerFlag.isEnabled) return
         if (KeyguardWmStateRefactor.isEnabled) {
             // This is handled in KeyguardSecurityContainerController and
             // StatusBarKeyguardViewManager, which calls the transition interactor to kick off a
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
index 197221a..fcf67d5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.sample
 import java.util.UUID
 import javax.inject.Inject
@@ -49,6 +50,8 @@
         fromState: KeyguardState,
         toState: KeyguardState,
     ) {
+        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        if (SceneContainerFlag.isEnabled) return
         val toScene =
             if (fromState == KeyguardState.GLANCEABLE_HUB) {
                 CommunalScenes.Blank
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index cf995fa..b1ef76e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -20,6 +20,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.content.Context
+import android.util.Log
 import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
@@ -36,9 +37,13 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -64,12 +69,7 @@
 
     /** Current BlueprintId */
     val blueprintId =
-        combine(
-            configurationInteractor.onAnyConfigurationChange,
-            fingerprintPropertyInteractor.propertiesInitialized.filter { it },
-            clockInteractor.currentClock,
-            shadeInteractor.shadeMode,
-        ) { _, _, _, shadeMode ->
+        shadeInteractor.shadeMode.map { shadeMode ->
             val useSplitShade = shadeMode == ShadeMode.Split && !ComposeLockscreen.isEnabled
             when {
                 useSplitShade -> SplitShadeKeyguardBlueprint.ID
@@ -77,8 +77,23 @@
             }
         }
 
+    private val refreshEvents: Flow<Unit> =
+        merge(
+            configurationInteractor.onAnyConfigurationChange,
+            fingerprintPropertyInteractor.propertiesInitialized
+                .filter { it }
+                .map { Unit }
+                .onEach {
+                    Log.d(
+                        "KeyguardBlueprintInteractor",
+                        "triggering refreshEvent from fpPropertiesInitialized"
+                    )
+                },
+        )
+
     init {
         applicationScope.launch { blueprintId.collect { transitionToBlueprint(it) } }
+        applicationScope.launch { refreshEvents.collect { refreshBlueprint() } }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
index 08d29d4..1aac1c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
@@ -25,6 +25,9 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -48,6 +51,7 @@
     transitionInteractor: KeyguardTransitionInteractor,
     val dismissInteractor: KeyguardDismissInteractor,
     @Application private val applicationScope: CoroutineScope,
+    sceneInteractor: SceneInteractor,
 ) {
     val dismissAction: Flow<DismissAction> = repository.dismissAction
 
@@ -72,7 +76,12 @@
             )
 
     private val finishedTransitionToGone: Flow<Unit> =
-        transitionInteractor.finishedKeyguardState.filter { it == GONE }.map {} // map to Unit
+        if (SceneContainerFlag.isEnabled) {
+            sceneInteractor.transitionState.filter { it.isIdle(Scenes.Gone) }.map {}
+        } else {
+            transitionInteractor.finishedKeyguardState.filter { it == GONE }.map {}
+        }
+
     val executeDismissAction: Flow<() -> KeyguardDone> =
         merge(
                 finishedTransitionToGone,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 88367f4..c44a40f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -63,10 +63,10 @@
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.combineTransform
+import kotlinx.coroutines.flow.debounce
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
@@ -98,6 +98,7 @@
 
     /** Bounds of the notification container. */
     val notificationContainerBounds: StateFlow<NotificationContainerBounds> by lazy {
+        SceneContainerFlag.assertInLegacyMode()
         combine(
                 _notificationPlaceholderBounds,
                 sharedNotificationContainerInteractor.get().configurationBasedDimensions,
@@ -116,6 +117,7 @@
     }
 
     fun setNotificationContainerBounds(position: NotificationContainerBounds) {
+        SceneContainerFlag.assertInLegacyMode()
         _notificationPlaceholderBounds.value = position
     }
 
@@ -179,13 +181,12 @@
                 isDreaming && isDozeOff(dozeTransitionModel.to)
             }
             .sample(powerInteractor.isAwake) { isAbleToDream, isAwake -> isAbleToDream && isAwake }
-            .flatMapLatest { isAbleToDream ->
-                flow {
-                    delay(50)
-                    emit(isAbleToDream)
-                }
-            }
-            .distinctUntilChanged()
+            .debounce(50L)
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
 
     /** Whether the keyguard is showing or not. */
     @Deprecated("Use KeyguardTransitionInteractor + KeyguardState")
@@ -229,7 +230,19 @@
     @JvmField val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow
 
     /** Whether the alternate bouncer is showing or not. */
-    val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
+    val alternateBouncerShowing: Flow<Boolean> =
+        bouncerRepository.alternateBouncerVisible.sample(isAbleToDream) {
+            alternateBouncerVisible,
+            isAbleToDream ->
+            if (isAbleToDream) {
+                // If the alternate bouncer will show over a dream, it is likely that the dream has
+                // requested a dismissal, which will stop the dream. By delaying this slightly, the
+                // DREAMING->LOCKSCREEN transition will now happen first, followed by
+                // LOCKSCREEN->ALTERNATE_BOUNCER.
+                delay(600L)
+            }
+            alternateBouncerVisible
+        }
 
     /** Observable for the [StatusBarState] */
     val statusBarState: Flow<StatusBarState> = repository.statusBarState
@@ -305,10 +318,12 @@
                     shadeRepository.legacyShadeExpansion.onStart { emit(0f) },
                     keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) },
                 ) { legacyShadeExpansion, goneValue ->
-                    if (goneValue == 1f || (goneValue == 0f && legacyShadeExpansion == 0f)) {
+                    val isLegacyShadeInResetPosition =
+                        legacyShadeExpansion == 0f || legacyShadeExpansion == 1f
+                    if (goneValue == 1f || (goneValue == 0f && isLegacyShadeInResetPosition)) {
                         // Reset the translation value
                         emit(0f)
-                    } else if (legacyShadeExpansion > 0f && legacyShadeExpansion < 1f) {
+                    } else if (!isLegacyShadeInResetPosition) {
                         // On swipe up, translate the keyguard to reveal the bouncer, OR a GONE
                         // transition is running, which means this is a swipe to dismiss. Values of
                         // 0f and 1f need to be ignored in the legacy shade expansion. These can
@@ -326,7 +341,11 @@
                     }
                 }
             }
-            .distinctUntilChanged()
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = 0f,
+            )
 
     val clockShouldBeCentered: Flow<Boolean> = repository.clockShouldBeCentered
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
index 20b7b2a..82255a0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -31,6 +31,7 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 
 /**
  * Distance over which the surface behind the keyguard is animated in during a Y-translation
@@ -102,8 +103,11 @@
      */
     private val isNotificationLaunchAnimationRunningOnKeyguard =
         notificationLaunchInteractor.isLaunchAnimationRunning
-            .sample(transitionInteractor.finishedKeyguardState)
-            .map { it != KeyguardState.GONE }
+            .sample(transitionInteractor.finishedKeyguardState, ::Pair)
+            .map { (animationRunning, finishedState) ->
+                animationRunning && finishedState != KeyguardState.GONE
+            }
+            .onStart { emit(false) }
 
     /**
      * Whether we're animating the surface, or a notification launch animation is running (which
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index e711edc..cf6942e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -19,6 +19,7 @@
 import com.android.keyguard.logging.KeyguardLogger
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.log.core.LogLevel.VERBOSE
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -26,6 +27,7 @@
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.debounce
 import kotlinx.coroutines.launch
 
 private val TAG = KeyguardTransitionAuditLogger::class.simpleName!!
@@ -41,6 +43,7 @@
     private val logger: KeyguardLogger,
     private val powerInteractor: PowerInteractor,
     private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
+    private val keyguardRootViewModel: KeyguardRootViewModel,
     private val shadeInteractor: ShadeInteractor,
     private val keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
 ) {
@@ -53,12 +56,6 @@
         }
 
         scope.launch {
-            sharedNotificationContainerViewModel
-                .getMaxNotifications { height, useExtraShelfSpace -> height.toInt() }
-                .collect { logger.log(TAG, VERBOSE, "Notif: max height in px", it) }
-        }
-
-        scope.launch {
             sharedNotificationContainerViewModel.isOnLockscreen.collect {
                 logger.log(TAG, VERBOSE, "Notif: isOnLockscreen", it)
             }
@@ -72,8 +69,8 @@
 
         if (!SceneContainerFlag.isEnabled) {
             scope.launch {
-                sharedNotificationContainerViewModel.bounds.collect {
-                    logger.log(TAG, VERBOSE, "Notif: bounds", it)
+                sharedNotificationContainerViewModel.bounds.debounce(20L).collect {
+                    logger.log(TAG, VERBOSE, "Notif: bounds (debounced)", it)
                 }
             }
         }
@@ -113,6 +110,18 @@
         }
 
         scope.launch {
+            keyguardInteractor.keyguardTranslationY.collect {
+                logger.log(TAG, VERBOSE, "keyguardTranslationY", it)
+            }
+        }
+
+        scope.launch {
+            keyguardRootViewModel.burnInModel.debounce(20L).collect {
+                logger.log(TAG, VERBOSE, "BurnInModel (debounced)", it)
+            }
+        }
+
+        scope.launch {
             keyguardInteractor.isKeyguardDismissible.collect {
                 logger.log(TAG, VERBOSE, "isDismissible", it)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
new file mode 100644
index 0000000..5ad7762
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.domain.interactor
+
+import android.util.Log
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/** Handles initialization of the KeyguardTransitionRepository on boot. */
+@SysUISingleton
+class KeyguardTransitionBootInteractor
+@Inject
+constructor(
+    @Application val scope: CoroutineScope,
+    val deviceEntryInteractor: DeviceEntryInteractor,
+    val deviceProvisioningInteractor: DeviceProvisioningInteractor,
+    val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    val repository: KeyguardTransitionRepository,
+) : CoreStartable {
+
+    /**
+     * Whether the lockscreen should be showing when the device starts up for the first time. If not
+     * then we'll seed the repository with a transition from OFF -> GONE.
+     */
+    @OptIn(ExperimentalCoroutinesApi::class)
+    private val showLockscreenOnBoot =
+        deviceProvisioningInteractor.isDeviceProvisioned.map { provisioned ->
+            (provisioned || deviceEntryInteractor.isAuthenticationRequired()) &&
+                deviceEntryInteractor.isLockscreenEnabled()
+        }
+
+    override fun start() {
+        scope.launch {
+            val state =
+                if (showLockscreenOnBoot.first()) {
+                    KeyguardState.LOCKSCREEN
+                } else {
+                    KeyguardState.GONE
+                }
+
+            if (
+                keyguardTransitionInteractor.currentTransitionInfoInternal.value.from !=
+                    KeyguardState.OFF
+            ) {
+                Log.e(
+                    "KeyguardTransitionInteractor",
+                    "showLockscreenOnBoot emitted, but we've already " +
+                        "transitioned to a state other than OFF. We'll respect that " +
+                        "transition, but this should not happen."
+                )
+            } else {
+                repository.emitInitialStepsFromOff(state)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index 91f8420..31b0bf7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -27,6 +27,7 @@
 constructor(
     private val interactors: Set<TransitionInteractor>,
     private val auditLogger: KeyguardTransitionAuditLogger,
+    private val bootInteractor: KeyguardTransitionBootInteractor,
 ) : CoreStartable {
 
     override fun start() {
@@ -51,6 +52,7 @@
             it.start()
         }
         auditLogger.start()
+        bootInteractor.start()
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index a18579d..c65dc30 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -17,7 +17,10 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.annotation.FloatRange
+import android.annotation.SuppressLint
 import android.util.Log
+import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -28,12 +31,16 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
-import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.util.kotlin.pairwise
+import java.util.UUID
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -68,14 +75,17 @@
     private val fromAlternateBouncerTransitionInteractor:
         dagger.Lazy<FromAlternateBouncerTransitionInteractor>,
     private val fromDozingTransitionInteractor: dagger.Lazy<FromDozingTransitionInteractor>,
+    private val sceneInteractor: dagger.Lazy<SceneInteractor>,
 ) {
-    private val transitionMap = mutableMapOf<Edge, MutableSharedFlow<TransitionStep>>()
+    private val transitionMap = mutableMapOf<Edge.StateToState, MutableSharedFlow<TransitionStep>>()
 
     /**
      * Numerous flows are derived from, or care directly about, the transition value in and out of a
      * single state. This prevent the redundant filters from running.
      */
     private val transitionValueCache = mutableMapOf<KeyguardState, MutableSharedFlow<Float>>()
+
+    @SuppressLint("SharedFlowCreation")
     private fun getTransitionValueFlow(state: KeyguardState): MutableSharedFlow<Float> {
         return transitionValueCache.getOrPut(state) {
             MutableSharedFlow<Float>(
@@ -90,6 +100,9 @@
     @Deprecated("Not performant - Use something else in this class")
     val transitions = repository.transitions
 
+    val transitionState: StateFlow<TransitionStep> =
+        transitions.stateIn(scope, SharingStarted.Eagerly, TransitionStep())
+
     /**
      * A pair of the most recent STARTED step, and the transition step immediately preceding it. The
      * transition framework enforces that the previous step is either a CANCELED or FINISHED step,
@@ -99,6 +112,7 @@
      * FINISHED. In the case of a CANCELED step, we can also figure out which state we were coming
      * from when we were canceled.
      */
+    @SuppressLint("SharedFlowCreation")
     val startedStepWithPrecedingStep =
         repository.transitions
             .pairwise()
@@ -119,11 +133,11 @@
         scope.launch {
             repository.transitions.collect {
                 // FROM->TO
-                transitionMap[Edge(it.from, it.to)]?.emit(it)
+                transitionMap[Edge.create(it.from, it.to)]?.emit(it)
                 // FROM->(ANY)
-                transitionMap[Edge(it.from, null)]?.emit(it)
+                transitionMap[Edge.create(it.from, null)]?.emit(it)
                 // (ANY)->TO
-                transitionMap[Edge(null, it.to)]?.emit(it)
+                transitionMap[Edge.create(null, it.to)]?.emit(it)
             }
         }
 
@@ -143,25 +157,70 @@
         }
     }
 
-    /** Given an [edge], return a SharedFlow to collect only relevant [TransitionStep]. */
-    fun getOrCreateFlow(edge: Edge): MutableSharedFlow<TransitionStep> {
-        return transitionMap.getOrPut(edge) {
-            MutableSharedFlow<TransitionStep>(
-                extraBufferCapacity = 10,
-                onBufferOverflow = BufferOverflow.DROP_OLDEST
-            )
+    fun transition(edge: Edge, edgeWithoutSceneContainer: Edge): Flow<TransitionStep> {
+        return transition(if (SceneContainerFlag.isEnabled) edge else edgeWithoutSceneContainer)
+    }
+
+    /** Given an [edge], return a Flow to collect only relevant [TransitionStep]s. */
+    @SuppressLint("SharedFlowCreation")
+    fun transition(edge: Edge): Flow<TransitionStep> {
+        edge.verifyValidKeyguardStates()
+        val mappedEdge = getMappedEdge(edge)
+
+        val flow: Flow<TransitionStep> =
+            transitionMap.getOrPut(mappedEdge) {
+                MutableSharedFlow(
+                    extraBufferCapacity = 10,
+                    onBufferOverflow = BufferOverflow.DROP_OLDEST
+                )
+            }
+
+        return if (SceneContainerFlag.isEnabled) {
+            flow.filter {
+                val fromScene =
+                    when (edge) {
+                        is Edge.StateToState -> edge.from?.mapToSceneContainerScene()
+                        is Edge.StateToScene -> edge.from.mapToSceneContainerScene()
+                        is Edge.SceneToState -> edge.from
+                    }
+
+                val toScene =
+                    when (edge) {
+                        is Edge.StateToState -> edge.to?.mapToSceneContainerScene()
+                        is Edge.StateToScene -> edge.to
+                        is Edge.SceneToState -> edge.to.mapToSceneContainerScene()
+                    }
+
+                fun SceneKey?.isLockscreenOrNull() = this == Scenes.Lockscreen || this == null
+
+                return@filter (fromScene.isLockscreenOrNull() && toScene.isLockscreenOrNull()) ||
+                    sceneInteractor.get().transitionState.value.isTransitioning(fromScene, toScene)
+            }
+        } else {
+            flow
         }
     }
 
     /**
-     * Receive all [TransitionStep] matching a filter of [from]->[to]. Allow nulls in order to match
-     * any transition, for instance (any)->GONE.
+     * Converts old KTF states to UNDEFINED when [SceneContainerFlag] is enabled.
+     *
+     * Does nothing otherwise.
+     *
+     * This method should eventually be removed when new code is only written for scene container.
+     * Even when all edges are ported today, there is still development on going in production that
+     * might utilize old states.
      */
-    fun transition(from: KeyguardState? = null, to: KeyguardState? = null): Flow<TransitionStep> {
-        if (from == null && to == null) {
-            throw IllegalArgumentException("from and to cannot both be null")
+    private fun getMappedEdge(edge: Edge): Edge.StateToState {
+        if (!SceneContainerFlag.isEnabled) return edge as Edge.StateToState
+        return when (edge) {
+            is Edge.StateToState ->
+                Edge.create(
+                    from = edge.from?.mapToSceneContainerState(),
+                    to = edge.to?.mapToSceneContainerState()
+                )
+            is Edge.SceneToState -> Edge.create(UNDEFINED, edge.to)
+            is Edge.StateToScene -> Edge.create(edge.from, UNDEFINED)
         }
-        return getOrCreateFlow(Edge(from = from, to = to))
     }
 
     /**
@@ -180,6 +239,7 @@
      * AOD<->* transition information, mapped to dozeAmount range of AOD (1f) <->
      * * (0f).
      */
+    @SuppressLint("SharedFlowCreation")
     val dozeAmountTransition: Flow<TransitionStep> =
         repository.transitions
             .filter { step -> step.from == AOD || step.to == AOD }
@@ -201,11 +261,22 @@
         repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
 
     /** The destination state of the last [TransitionState.STARTED] transition. */
+    @SuppressLint("SharedFlowCreation")
     val startedKeyguardState: SharedFlow<KeyguardState> =
         startedKeyguardTransitionStep
             .map { step -> step.to }
             .shareIn(scope, SharingStarted.Eagerly, replay = 1)
 
+    val currentTransitionInfo: StateFlow<TransitionInfo> = repository.currentTransitionInfoInternal
+
+    /** The from state of the last [TransitionState.STARTED] transition. */
+    // TODO: is it performant to have several SharedFlows side by side instead of one?
+    @SuppressLint("SharedFlowCreation")
+    val startedKeyguardFromState: SharedFlow<KeyguardState> =
+        startedKeyguardTransitionStep
+            .map { step -> step.from }
+            .shareIn(scope, SharingStarted.Eagerly, replay = 1)
+
     /** Which keyguard state to use when the device goes to sleep. */
     val asleepKeyguardState: StateFlow<KeyguardState> =
         keyguardRepository.isAodAvailable
@@ -243,6 +314,7 @@
      * sufficient. However, if you're having issues with state *during* transitions started after
      * one or more canceled transitions, you probably need to use [currentKeyguardState].
      */
+    @SuppressLint("SharedFlowCreation")
     val finishedKeyguardState: SharedFlow<KeyguardState> =
         finishedKeyguardTransitionStep
             .map { step -> step.to }
@@ -344,30 +416,37 @@
     val isInTransitionToAnyState = isInTransitionWhere({ true }, { true })
 
     fun transitionStepsFromState(fromState: KeyguardState): Flow<TransitionStep> {
-        return getOrCreateFlow(Edge(from = fromState, to = null))
+        return transition(Edge.create(from = fromState, to = null))
     }
 
     fun transitionStepsToState(toState: KeyguardState): Flow<TransitionStep> {
-        return getOrCreateFlow(Edge(from = null, to = toState))
+        return transition(Edge.create(from = null, to = toState))
     }
 
     /**
      * Called to start a transition that will ultimately dismiss the keyguard from the current
      * state.
+     *
+     * This is called exclusively by sources that can authoritatively say we should be unlocked,
+     * including KeyguardSecurityContainerController and WindowManager.
      */
-    fun startDismissKeyguardTransition() {
-        when (val startedState = startedKeyguardState.replayCache.last()) {
+    fun startDismissKeyguardTransition(reason: String = "") {
+        // TODO(b/336576536): Check if adaptation for scene framework is needed
+        if (SceneContainerFlag.isEnabled) return
+        Log.d(TAG, "#startDismissKeyguardTransition(reason=$reason)")
+        when (val startedState = currentTransitionInfoInternal.value.to) {
             LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
             PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
             ALTERNATE_BOUNCER ->
                 fromAlternateBouncerTransitionInteractor.get().dismissAlternateBouncer()
             AOD -> fromAodTransitionInteractor.get().dismissAod()
             DOZING -> fromDozingTransitionInteractor.get().dismissFromDozing()
-            else ->
-                Log.e(
-                    "KeyguardTransitionInteractor",
-                    "We don't know how to dismiss keyguard from state $startedState."
+            KeyguardState.GONE ->
+                Log.i(
+                    TAG,
+                    "Already transitioning to GONE; ignoring startDismissKeyguardTransition."
                 )
+            else -> Log.e(TAG, "We don't know how to dismiss keyguard from state $startedState.")
         }
     }
 
@@ -375,7 +454,7 @@
     fun isInTransitionToState(
         state: KeyguardState,
     ): Flow<Boolean> {
-        return getOrCreateFlow(Edge(from = null, to = state))
+        return transition(Edge.create(from = null, to = state))
             .mapLatest { it.transitionState.isTransitioning() }
             .onStart { emit(false) }
             .distinctUntilChanged()
@@ -384,12 +463,16 @@
     /**
      * Whether we're in a transition to and from the given [KeyguardState]s, but haven't yet
      * completed it.
+     *
+     * Provide [edgeWithoutSceneContainer] when the edge is different from what it is without it. If
+     * the edges are equal before and after the flag it is sufficient to provide just [edge].
      */
-    fun isInTransition(
-        from: KeyguardState,
-        to: KeyguardState,
-    ): Flow<Boolean> {
-        return getOrCreateFlow(Edge(from = from, to = to))
+    fun isInTransition(edge: Edge, edgeWithoutSceneContainer: Edge? = null): Flow<Boolean> {
+        return if (SceneContainerFlag.isEnabled) {
+                transition(edge)
+            } else {
+                transition(edgeWithoutSceneContainer ?: edge)
+            }
             .mapLatest { it.transitionState.isTransitioning() }
             .onStart { emit(false) }
             .distinctUntilChanged()
@@ -401,7 +484,7 @@
     fun isInTransitionFromState(
         state: KeyguardState,
     ): Flow<Boolean> {
-        return getOrCreateFlow(Edge(from = state, to = null))
+        return transition(Edge.create(from = state, to = null))
             .mapLatest { it.transitionState.isTransitioning() }
             .onStart { emit(false) }
             .distinctUntilChanged()
@@ -452,7 +535,7 @@
      * If you only care about a single state for both from and to, instead use the optimized
      * [isInTransition].
      */
-    fun isInTransitionWhere(
+    private fun isInTransitionWhere(
         fromToStatePredicate: (KeyguardState, KeyguardState) -> Boolean
     ): Flow<Boolean> {
         return repository.transitions
@@ -489,7 +572,23 @@
         return startedKeyguardState.replayCache.last()
     }
 
+    fun getStartedFromState(): KeyguardState {
+        return startedKeyguardFromState.replayCache.last()
+    }
+
     fun getFinishedState(): KeyguardState {
         return finishedKeyguardState.replayCache.last()
     }
+
+    suspend fun startTransition(info: TransitionInfo) = repository.startTransition(info)
+
+    fun updateTransition(
+        transitionId: UUID,
+        @FloatRange(from = 0.0, to = 1.0) value: Float,
+        state: TransitionState
+    ) = repository.updateTransition(transitionId, value, state)
+
+    companion object {
+        private val TAG = KeyguardTransitionInteractor::class.simpleName
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 2d944c6..9443570 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -103,6 +103,7 @@
             KeyguardState.LOCKSCREEN -> true
             KeyguardState.GONE -> true
             KeyguardState.OCCLUDED -> true
+            KeyguardState.UNDEFINED -> true
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index d95c38e..3c66186 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import com.android.systemui.CoreStartable
+import com.android.systemui.keyguard.domain.interactor.scenetransition.LockscreenSceneTransitionInteractor
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
@@ -31,6 +32,13 @@
     abstract fun bind(impl: KeyguardTransitionCoreStartable): CoreStartable
 
     @Binds
+    @IntoMap
+    @ClassKey(LockscreenSceneTransitionInteractor::class)
+    abstract fun bindLockscreenSceneTransitionInteractor(
+        impl: LockscreenSceneTransitionInteractor
+    ): CoreStartable
+
+    @Binds
     @IntoSet
     abstract fun fromPrimaryBouncer(
         impl: FromPrimaryBouncerTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index b2a24ca..323ceef 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -229,6 +229,7 @@
                         startTransitionTo(
                             toState = KeyguardState.OCCLUDED,
                             modeOnCanceled = TransitionModeOnCanceled.RESET,
+                            ownerReason = "keyguardInteractor.onCameraLaunchDetected",
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index bb2eeb7..1e2db7c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -16,11 +16,17 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
+import com.android.systemui.util.kotlin.pairwise
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -42,6 +48,7 @@
     fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
     fromAlternateBouncerInteractor: FromAlternateBouncerTransitionInteractor,
     notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
+    sceneInteractor: SceneInteractor,
 ) {
     private val defaultSurfaceBehindVisibility =
         transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible)
@@ -103,21 +110,42 @@
      * animation. This is used to keep the RemoteAnimationTarget alive until we're done using it.
      */
     val usingKeyguardGoingAwayAnimation: Flow<Boolean> =
-        combine(
-                transitionInteractor.isInTransitionToState(KeyguardState.GONE),
-                transitionInteractor.finishedKeyguardState,
-                surfaceBehindInteractor.isAnimatingSurface,
-                notificationLaunchAnimationInteractor.isLaunchAnimationRunning,
-            ) { isInTransitionToGone, finishedState, isAnimatingSurface, notifLaunchRunning ->
-                // Using the animation if we're animating it directly, or if the
-                // ActivityLaunchAnimator is in the process of animating it.
-                val animationsRunning = isAnimatingSurface || notifLaunchRunning
-                // We may still be animating the surface after the keyguard is fully GONE, since
-                // some animations (like the translation spring) are not tied directly to the
-                // transition step amount.
-                isInTransitionToGone || (finishedState == KeyguardState.GONE && animationsRunning)
-            }
-            .distinctUntilChanged()
+        if (SceneContainerFlag.isEnabled) {
+            combine(
+                    sceneInteractor.transitionState,
+                    surfaceBehindInteractor.isAnimatingSurface,
+                    notificationLaunchAnimationInteractor.isLaunchAnimationRunning,
+                ) { transition, isAnimatingSurface, isLaunchAnimationRunning ->
+                    // Using the animation if we're animating it directly, or if the
+                    // ActivityLaunchAnimator is in the process of animating it.
+                    val isAnyAnimationRunning = isAnimatingSurface || isLaunchAnimationRunning
+                    // We may still be animating the surface after the keyguard is fully GONE, since
+                    // some animations (like the translation spring) are not tied directly to the
+                    // transition step amount.
+                    transition.isTransitioning(to = Scenes.Gone) ||
+                        (isAnyAnimationRunning &&
+                            (transition.isIdle(Scenes.Gone) ||
+                                transition.isTransitioning(from = Scenes.Gone)))
+                }
+                .distinctUntilChanged()
+        } else {
+            combine(
+                    transitionInteractor.isInTransitionToState(KeyguardState.GONE),
+                    transitionInteractor.finishedKeyguardState,
+                    surfaceBehindInteractor.isAnimatingSurface,
+                    notificationLaunchAnimationInteractor.isLaunchAnimationRunning,
+                ) { isInTransitionToGone, finishedState, isAnimatingSurface, notifLaunchRunning ->
+                    // Using the animation if we're animating it directly, or if the
+                    // ActivityLaunchAnimator is in the process of animating it.
+                    val animationsRunning = isAnimatingSurface || notifLaunchRunning
+                    // We may still be animating the surface after the keyguard is fully GONE, since
+                    // some animations (like the translation spring) are not tied directly to the
+                    // transition step amount.
+                    isInTransitionToGone ||
+                        (finishedState == KeyguardState.GONE && animationsRunning)
+                }
+                .distinctUntilChanged()
+        }
 
     /**
      * Whether the lockscreen is visible, from the Window Manager (WM) perspective.
@@ -127,28 +155,44 @@
      * want to know if the AOD/clock/notifs/etc. are visible.
      */
     val lockscreenVisibility: Flow<Boolean> =
-        transitionInteractor.currentKeyguardState
-            .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair)
-            .map { (currentState, startedWithPrev) ->
-                val startedFromStep = startedWithPrev?.previousValue
-                val startedStep = startedWithPrev?.newValue
-                val returningToGoneAfterCancellation =
-                    startedStep?.to == KeyguardState.GONE &&
-                        startedFromStep?.transitionState == TransitionState.CANCELED &&
-                        startedFromStep.from == KeyguardState.GONE
+        if (SceneContainerFlag.isEnabled) {
+            sceneInteractor.transitionState
+                .pairwise(ObservableTransitionState.Idle(Scenes.Lockscreen))
+                .map { (prevTransitionState, transitionState) ->
+                    val isReturningToGoneAfterCancellation =
+                        prevTransitionState.isTransitioning(from = Scenes.Gone) &&
+                            transitionState.isTransitioning(to = Scenes.Gone)
+                    val isNotOnGone =
+                        !transitionState.isTransitioning(from = Scenes.Gone) &&
+                            !transitionState.isIdle(Scenes.Gone)
 
-                if (!returningToGoneAfterCancellation) {
-                    // By default, apply the lockscreen visibility of the current state.
-                    KeyguardState.lockscreenVisibleInState(currentState)
-                } else {
-                    // If we're transitioning to GONE after a prior canceled transition from GONE,
-                    // then this is the camera launch transition from an asleep state back to GONE.
-                    // We don't want to show the lockscreen since we're aborting the lock and going
-                    // back to GONE.
-                    KeyguardState.lockscreenVisibleInState(KeyguardState.GONE)
+                    isNotOnGone && !isReturningToGoneAfterCancellation
                 }
-            }
-            .distinctUntilChanged()
+                .distinctUntilChanged()
+        } else {
+            transitionInteractor.currentKeyguardState
+                .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair)
+                .map { (currentState, startedWithPrev) ->
+                    val startedFromStep = startedWithPrev?.previousValue
+                    val startedStep = startedWithPrev?.newValue
+                    val returningToGoneAfterCancellation =
+                        startedStep?.to == KeyguardState.GONE &&
+                            startedFromStep?.transitionState == TransitionState.CANCELED &&
+                            startedFromStep.from == KeyguardState.GONE
+
+                    if (!returningToGoneAfterCancellation) {
+                        // By default, apply the lockscreen visibility of the current state.
+                        KeyguardState.lockscreenVisibleInState(currentState)
+                    } else {
+                        // If we're transitioning to GONE after a prior canceled transition from
+                        // GONE, then this is the camera launch transition from an asleep state back
+                        // to GONE. We don't want to show the lockscreen since we're aborting the
+                        // lock and going back to GONE.
+                        KeyguardState.lockscreenVisibleInState(KeyguardState.GONE)
+                    }
+                }
+                .distinctUntilChanged()
+        }
 
     /**
      * Whether always-on-display (AOD) is visible when the lockscreen is visible, from window
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
new file mode 100644
index 0000000..3baeb76
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
@@ -0,0 +1,231 @@
+/*
+ * 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.domain.interactor.scenetransition
+
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.LockscreenSceneTransitionRepository
+import com.android.systemui.keyguard.data.repository.LockscreenSceneTransitionRepository.Companion.DEFAULT_STATE
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.pairwise
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+/**
+ * This class listens to scene framework scene transitions and manages keyguard transition framework
+ * (KTF) states accordingly.
+ *
+ * There are a few rules:
+ * - When scene framework is on a scene outside of Lockscreen, then KTF is in state UNDEFINED
+ * - When scene framework is on Lockscreen, KTF is allowed to change its scenes freely
+ * - When scene framework is transitioning away from Lockscreen, then KTF transitions to UNDEFINED
+ *   and shares its progress.
+ * - When scene framework is transitioning to Lockscreen, then KTF starts a transition to LOCKSCREEN
+ *   but it is allowed to interrupt this transition and transition to other internal KTF states
+ *
+ * There are a few notable differences between SceneTransitionLayout (STL) and KTF that require
+ * special treatment when synchronizing both state machines.
+ * - STL does not emit cancelations as KTF does
+ * - Both STL and KTF require state continuity, though the rules from where starting the next
+ *   transition is allowed is different on each side:
+ *     - STL has a concept of "currentScene" which can be chosen to be either A or B in a A -> B
+ *       transition. The currentScene determines which transition can be started next. In KTF the
+ *       currentScene is always the `to` state. Which means transitions can only be started from B.
+ *       This also holds true when A -> B was canceled: the next transition needs to start from B.
+ *     - KTF can not settle back in its from scene, instead it needs to cancel and start a reversed
+ *       transition.
+ */
+@SysUISingleton
+class LockscreenSceneTransitionInteractor
+@Inject
+constructor(
+    val transitionInteractor: KeyguardTransitionInteractor,
+    @Application private val applicationScope: CoroutineScope,
+    private val sceneInteractor: SceneInteractor,
+    private val repository: LockscreenSceneTransitionRepository,
+) : CoreStartable, SceneInteractor.OnSceneAboutToChangeListener {
+
+    private var currentTransitionId: UUID? = null
+    private var progressJob: Job? = null
+
+    override fun start() {
+        sceneInteractor.registerSceneStateProcessor(this)
+        listenForSceneTransitionProgress()
+    }
+
+    override fun onSceneAboutToChange(toScene: SceneKey, sceneState: Any?) {
+        if (toScene != Scenes.Lockscreen || sceneState == null) return
+        if (sceneState !is KeyguardState) {
+            throw IllegalArgumentException("Lockscreen sceneState needs to be a KeyguardState.")
+        }
+        repository.nextLockscreenTargetState.value = sceneState
+    }
+
+    private fun listenForSceneTransitionProgress() {
+        applicationScope.launch {
+            sceneInteractor.transitionState
+                .pairwise(ObservableTransitionState.Idle(Scenes.Lockscreen))
+                .collect { (prevTransition, transition) ->
+                    when (transition) {
+                        is ObservableTransitionState.Idle -> handleIdle(prevTransition, transition)
+                        is ObservableTransitionState.Transition -> handleTransition(transition)
+                    }
+                }
+        }
+    }
+
+    private suspend fun handleIdle(
+        prevTransition: ObservableTransitionState,
+        idle: ObservableTransitionState.Idle
+    ) {
+        if (currentTransitionId == null) return
+        if (prevTransition !is ObservableTransitionState.Transition) return
+
+        if (idle.currentScene == prevTransition.toScene) {
+            finishCurrentTransition()
+        } else {
+            val targetState =
+                if (idle.currentScene == Scenes.Lockscreen) {
+                    transitionInteractor.getStartedFromState()
+                } else {
+                    UNDEFINED
+                }
+            finishReversedTransitionTo(targetState)
+        }
+    }
+
+    private fun finishCurrentTransition() {
+        transitionInteractor.updateTransition(currentTransitionId!!, 1f, FINISHED)
+        resetTransitionData()
+    }
+
+    private suspend fun finishReversedTransitionTo(state: KeyguardState) {
+        val newTransition =
+            TransitionInfo(
+                ownerName = this::class.java.simpleName,
+                from = transitionInteractor.currentTransitionInfo.value.to,
+                to = state,
+                animator = null,
+                modeOnCanceled = TransitionModeOnCanceled.REVERSE
+            )
+        currentTransitionId = transitionInteractor.startTransition(newTransition)
+        transitionInteractor.updateTransition(currentTransitionId!!, 1f, FINISHED)
+        resetTransitionData()
+    }
+
+    private fun resetTransitionData() {
+        progressJob?.cancel()
+        progressJob = null
+        currentTransitionId = null
+    }
+
+    private suspend fun handleTransition(transition: ObservableTransitionState.Transition) {
+        if (transition.fromScene == Scenes.Lockscreen) {
+            if (currentTransitionId != null) {
+                val currentToState = transitionInteractor.currentTransitionInfo.value.to
+                if (currentToState == UNDEFINED) {
+                    transitionKtfTo(transitionInteractor.getStartedFromState())
+                }
+            }
+            startTransitionFromLockscreen()
+            collectProgress(transition)
+        } else if (transition.toScene == Scenes.Lockscreen) {
+            if (currentTransitionId != null) {
+                transitionKtfTo(UNDEFINED)
+            }
+            startTransitionToLockscreen()
+            collectProgress(transition)
+        } else {
+            transitionKtfTo(UNDEFINED)
+        }
+    }
+
+    private suspend fun transitionKtfTo(state: KeyguardState) {
+        // TODO(b/330311871): This is based on a sharedFlow and thus might not be up-to-date and
+        //  cause a race condition. (There is no known scenario that is currently affected.)
+        val currentTransition = transitionInteractor.transitionState.value
+        if (currentTransition.isFinishedIn(state)) {
+            // This is already the state we want to be in
+            resetTransitionData()
+        } else if (currentTransition.isTransitioning(to = state)) {
+            finishCurrentTransition()
+        } else {
+            finishReversedTransitionTo(state)
+        }
+    }
+
+    private fun collectProgress(transition: ObservableTransitionState.Transition) {
+        progressJob?.cancel()
+        progressJob = applicationScope.launch { transition.progress.collect { updateProgress(it) } }
+    }
+
+    private suspend fun startTransitionToLockscreen() {
+        val newTransition =
+            TransitionInfo(
+                ownerName = this::class.java.simpleName,
+                from = UNDEFINED,
+                to = repository.nextLockscreenTargetState.value,
+                animator = null,
+                modeOnCanceled = TransitionModeOnCanceled.RESET
+            )
+        repository.nextLockscreenTargetState.value = DEFAULT_STATE
+        startTransition(newTransition)
+    }
+
+    private suspend fun startTransitionFromLockscreen() {
+        val currentState = transitionInteractor.currentTransitionInfo.value.to
+        val newTransition =
+            TransitionInfo(
+                ownerName = this::class.java.simpleName,
+                from = currentState,
+                to = UNDEFINED,
+                animator = null,
+                modeOnCanceled = TransitionModeOnCanceled.RESET
+            )
+        startTransition(newTransition)
+    }
+
+    private suspend fun startTransition(transitionInfo: TransitionInfo) {
+        if (currentTransitionId != null) {
+            resetTransitionData()
+        }
+        currentTransitionId = transitionInteractor.startTransition(transitionInfo)
+    }
+
+    private fun updateProgress(progress: Float) {
+        if (currentTransitionId == null) return
+        transitionInteractor.updateTransition(
+            currentTransitionId!!,
+            progress.coerceIn(0f, 1f),
+            RUNNING
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt
index a0f9be6..4f516f5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt
@@ -15,8 +15,98 @@
  */
 package com.android.systemui.keyguard.shared.model
 
-/** FROM -> TO keyguard transition. null values are allowed to signify FROM -> *, or * -> TO */
-data class Edge(
-    val from: KeyguardState?,
-    val to: KeyguardState?,
-)
+import android.util.Log
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+
+/**
+ * Represents an edge either between two Keyguard Transition Framework states (KTF) or a KTF state
+ * and a scene container scene. Passing [null] in either [from] or [to] indicates a wildcard.
+ *
+ * Wildcards are not allowed for transitions involving a scene. Use [sceneInteractor] directly
+ * instead. Reason: [TransitionStep]s are not emitted for every edge leading into/out of a scene.
+ * For example: Lockscreen -> Gone would be emitted as LOCKSCREEN -> UNDEFINED but Bouncer -> Gone
+ * would not emit anything.
+ */
+sealed class Edge {
+
+    fun verifyValidKeyguardStates() {
+        when (this) {
+            is StateToState -> verifyValidKeyguardStates(from, to)
+            is SceneToState -> verifyValidKeyguardStates(null, to)
+            is StateToScene -> verifyValidKeyguardStates(from, null)
+        }
+    }
+
+    private fun verifyValidKeyguardStates(from: KeyguardState?, to: KeyguardState?) {
+        val mappedFrom = from?.mapToSceneContainerState()
+        val mappedTo = to?.mapToSceneContainerState()
+
+        val fromChanged = from != mappedFrom
+        val toChanged = to != mappedTo
+
+        if (SceneContainerFlag.isEnabled) {
+            if (fromChanged && toChanged) {
+                // TODO:(b/330311871) As we come close to having all current edges converted these
+                //  error messages can be converted to throw such that future developers fail early
+                //  when they introduce invalid edges.
+                Log.e(
+                    TAG,
+                    """
+                    The edge ${from?.name} => ${to?.name} was automatically converted to
+                    ${mappedFrom?.name} => ${mappedTo?.name} but does not exist anymore in KTF.
+                    Please remove or port this edge to scene container."""
+                        .trimIndent(),
+                )
+            } else if ((fromChanged && to == null) || (toChanged && from == null)) {
+                Log.e(
+                    TAG,
+                    """
+                    The edge ${from?.name} => ${to?.name} was automatically converted to
+                    ${mappedFrom?.name} => ${mappedTo?.name}. Wildcards are not allowed together
+                    with UNDEFINED because it will only be tracking edges leading in and out of
+                    the Lockscreen scene but miss others. Please remove or port this edge."""
+                        .trimIndent(),
+                    Exception()
+                )
+            } else if (fromChanged || toChanged) {
+                Log.w(
+                    TAG,
+                    """
+                    The edge ${from?.name} => ${to?.name} was automatically converted to
+                    ${mappedFrom?.name} => ${mappedTo?.name} it probably exists but needs explicit
+                    conversion. Please remove or port this edge to scene container."""
+                        .trimIndent(),
+                )
+            }
+        } else {
+            if (from == UNDEFINED || to == UNDEFINED) {
+                Log.e(
+                    TAG,
+                    "UNDEFINED should not be used when scene container is disabled",
+                )
+            }
+        }
+    }
+
+    data class StateToState(val from: KeyguardState?, val to: KeyguardState?) : Edge() {
+        init {
+            check(!(from == null && to == null)) { "to and from can't both be null" }
+        }
+    }
+
+    data class StateToScene(val from: KeyguardState, val to: SceneKey) : Edge()
+
+    data class SceneToState(val from: SceneKey, val to: KeyguardState) : Edge()
+
+    companion object {
+        private const val TAG = "Edge"
+
+        fun create(from: KeyguardState? = null, to: KeyguardState? = null) = StateToState(from, to)
+
+        fun create(from: KeyguardState, to: SceneKey) = StateToScene(from, to)
+
+        fun create(from: SceneKey, to: KeyguardState) = SceneToState(from, to)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index 7d05539..6a2bb5f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -15,9 +15,12 @@
  */
 package com.android.systemui.keyguard.shared.model
 
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.scene.shared.model.Scenes
+
 /** List of all possible states to transition to/from */
 enum class KeyguardState {
-    /*
+    /**
      * The display is completely off, as well as any sensors that would trigger the device to wake
      * up.
      */
@@ -29,13 +32,13 @@
      * notifications is enabled, allowing the device to quickly wake up.
      */
     DOZING,
-    /*
+    /**
      * A device state after the device times out, which can be from both LOCKSCREEN or GONE states.
      * DOZING is an example of special version of this state. Dreams may be implemented by third
      * parties to present their own UI over keyguard, like a screensaver.
      */
     DREAMING,
-    /*
+    /**
      * A device state after the device times out, which can be from both LOCKSCREEN or GONE states.
      * It is a special version of DREAMING state but not DOZING. The active dream will be windowless
      * and hosted in the lockscreen.
@@ -47,17 +50,17 @@
      * low-power mode without a UI, then it is DOZING.
      */
     AOD,
-    /*
+    /**
      * The security screen prompt containing UI to prompt the user to use a biometric credential
      * (ie: fingerprint). When supported, this may show before showing the primary bouncer.
      */
     ALTERNATE_BOUNCER,
-    /*
+    /**
      * The security screen prompt UI, containing PIN, Password, Pattern for the user to verify their
      * credentials.
      */
     PRIMARY_BOUNCER,
-    /*
+    /**
      * Device is actively displaying keyguard UI and is not in low-power mode. Device may be
      * unlocked if SWIPE security method is used, or if face lockscreen bypass is false.
      */
@@ -68,17 +71,56 @@
      * or dream, as well as swipe down for the notifications and up for the bouncer.
      */
     GLANCEABLE_HUB,
-    /*
-     * Keyguard is no longer visible. In most cases the user has just authenticated and keyguard
-     * is being removed, but there are other cases where the user is swiping away keyguard, such as
+    /**
+     * Keyguard is no longer visible. In most cases the user has just authenticated and keyguard is
+     * being removed, but there are other cases where the user is swiping away keyguard, such as
      * with SWIPE security method or face unlock without bypass.
      */
     GONE,
-    /*
-     * An activity is displaying over the keyguard.
+    /**
+     * Only used in scene framework. This means we are currently on any scene framework scene that
+     * is not Lockscreen. Transitions to and from UNDEFINED are always bound to the
+     * [SceneTransitionLayout] scene transition that either transitions to or from the Lockscreen
+     * scene. These transitions are automatically handled by [LockscreenSceneTransitionInteractor].
      */
+    UNDEFINED,
+    /** An activity is displaying over the keyguard. */
     OCCLUDED;
 
+    fun mapToSceneContainerState(): KeyguardState {
+        return when (this) {
+            OFF,
+            DOZING,
+            DREAMING,
+            DREAMING_LOCKSCREEN_HOSTED,
+            AOD,
+            ALTERNATE_BOUNCER,
+            OCCLUDED,
+            LOCKSCREEN -> this
+            GLANCEABLE_HUB,
+            PRIMARY_BOUNCER,
+            GONE,
+            UNDEFINED -> UNDEFINED
+        }
+    }
+
+    fun mapToSceneContainerScene(): SceneKey? {
+        return when (this) {
+            OFF,
+            DOZING,
+            DREAMING,
+            DREAMING_LOCKSCREEN_HOSTED,
+            AOD,
+            ALTERNATE_BOUNCER,
+            OCCLUDED,
+            LOCKSCREEN -> Scenes.Lockscreen
+            GLANCEABLE_HUB -> Scenes.Communal
+            PRIMARY_BOUNCER -> Scenes.Bouncer
+            GONE -> Scenes.Gone
+            UNDEFINED -> null
+        }
+    }
+
     companion object {
 
         /** Whether the lockscreen is visible when we're FINISHED in the given state. */
@@ -109,6 +151,7 @@
                 LOCKSCREEN -> true
                 GONE -> true
                 OCCLUDED -> true
+                UNDEFINED -> true
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
index 0fa6f4f..2b4c4af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
@@ -30,4 +30,12 @@
         value: Float,
         transitionState: TransitionState,
     ) : this(info.from, info.to, value, transitionState, info.ownerName)
+
+    fun isTransitioning(from: KeyguardState? = null, to: KeyguardState? = null): Boolean {
+        return (from == null || this.from == from) && (to == null || this.to == to)
+    }
+
+    fun isFinishedIn(state: KeyguardState): Boolean {
+        return to == state && transitionState == TransitionState.FINISHED
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 735b109..23aa21c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import javax.inject.Inject
 import kotlin.math.max
 import kotlin.math.min
@@ -52,20 +53,20 @@
     /** Invoke once per transition between FROM->TO states to get access to a shared flow. */
     fun setup(
         duration: Duration,
-        from: KeyguardState?,
-        to: KeyguardState?,
+        edge: Edge,
     ): FlowBuilder {
-        if (from == null && to == null) {
-            throw IllegalArgumentException("from and to are both null")
-        }
-
-        return FlowBuilder(duration, Edge(from, to))
+        return FlowBuilder(duration, edge)
     }
 
     inner class FlowBuilder(
         private val transitionDuration: Duration,
         private val edge: Edge,
     ) {
+        fun setupWithoutSceneContainer(edge: Edge.StateToState): FlowBuilder {
+            if (SceneContainerFlag.isEnabled) return this
+            return setup(this.transitionDuration, edge)
+        }
+
         /**
          * Transitions will occur over a [transitionDuration] with [TransitionStep]s being emitted
          * in the range of [0, 1]. View animations should begin and end within a subset of this
@@ -117,7 +118,7 @@
             if (!duration.isPositive()) {
                 throw IllegalArgumentException("duration must be a positive number: $duration")
             }
-            if ((startTime + duration).compareTo(transitionDuration) > 0) {
+            if ((startTime + duration) > transitionDuration) {
                 throw IllegalArgumentException(
                     "startTime($startTime) + duration($duration) must be" +
                         " <= transitionDuration($transitionDuration)"
@@ -153,7 +154,7 @@
             }
 
             return transitionInteractor
-                .getOrCreateFlow(edge)
+                .transition(edge)
                 .map { step ->
                     StateToValue(
                             from = step.from,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index 4f00495..1c7b4d9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -22,6 +22,8 @@
 import android.util.StateSet
 import android.view.HapticFeedbackConstants
 import android.view.View
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
 import androidx.core.view.isInvisible
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
@@ -61,6 +63,7 @@
         bgViewModel: DeviceEntryBackgroundViewModel,
         falsingManager: FalsingManager,
         vibratorHelper: VibratorHelper,
+        overrideColor: Color? = null,
     ) {
         DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()
         val longPressHandlingView = view.longPressHandlingView
@@ -76,7 +79,7 @@
                         view,
                         HapticFeedbackConstants.CONFIRM,
                     )
-                    applicationScope.launch { viewModel.onLongPress() }
+                    applicationScope.launch { viewModel.onUserInteraction() }
                 }
             }
 
@@ -116,6 +119,17 @@
                 launch("$TAG#viewModel.accessibilityDelegateHint") {
                     viewModel.accessibilityDelegateHint.collect { hint ->
                         view.accessibilityHintType = hint
+                        if (hint != DeviceEntryIconView.AccessibilityHintType.NONE) {
+                            view.setOnClickListener {
+                                vibratorHelper.performHapticFeedback(
+                                    view,
+                                    HapticFeedbackConstants.CONFIRM,
+                                )
+                                applicationScope.launch { viewModel.onUserInteraction() }
+                            }
+                        } else {
+                            view.setOnClickListener(null)
+                        }
                     }
                 }
                 launch("$TAG#viewModel.useBackgroundProtection") {
@@ -157,7 +171,8 @@
                                     viewModel.type.contentDescriptionResId
                                 )
                         }
-                        fgIconView.imageTintList = ColorStateList.valueOf(viewModel.tint)
+                        fgIconView.imageTintList =
+                            ColorStateList.valueOf(overrideColor?.toArgb() ?: viewModel.tint)
                         fgIconView.setPadding(
                             viewModel.padding,
                             viewModel.padding,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index ed5d53c..c846cbe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -33,14 +33,18 @@
 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
 import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.clocks.AodClockBurnInModel
 import com.android.systemui.plugins.clocks.ClockController
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
 object KeyguardClockViewBinder {
     private val TAG = KeyguardClockViewBinder::class.simpleName!!
     // When changing to new clock, we need to remove old clock views from burnInLayer
     private var lastClock: ClockController? = null
+
     @JvmStatic
     fun bind(
         clockSection: ClockSection,
@@ -48,6 +52,7 @@
         viewModel: KeyguardClockViewModel,
         keyguardClockInteractor: KeyguardClockInteractor,
         blueprintInteractor: KeyguardBlueprintInteractor,
+        rootViewModel: KeyguardRootViewModel,
     ) {
         keyguardRootView.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.CREATED) {
@@ -105,6 +110,28 @@
                         }
                     }
                 }
+
+                launch {
+                    if (!MigrateClocksToBlueprint.isEnabled) return@launch
+                    combine(
+                            rootViewModel.translationX,
+                            rootViewModel.translationY,
+                            rootViewModel.scale,
+                            ::Triple
+                        )
+                        .collect { (translationX, translationY, scale) ->
+                            viewModel.currentClock.value
+                                ?.largeClock
+                                ?.layout
+                                ?.applyAodBurnIn(
+                                    AodClockBurnInModel(
+                                        translationX = translationX.value!!,
+                                        translationY = translationY,
+                                        scale = scale.scale
+                                    )
+                                )
+                        }
+                }
             }
         }
     }
@@ -117,17 +144,16 @@
     ) {
         val burnInLayer = viewModel.burnInLayer
         val clockController = viewModel.currentClock.value
+        // Large clocks won't be added to or removed from burn in layer
+        // Weather large clock has customized burn in preventing mechanism
+        // Non-weather large clock will only scale and translate vertically
         clockController?.let { clock ->
             when (clockSize) {
                 ClockSize.LARGE -> {
                     clock.smallClock.layout.views.forEach { burnInLayer?.removeView(it) }
-                    if (clock.config.useAlternateSmartspaceAODTransition) {
-                        clock.largeClock.layout.views.forEach { burnInLayer?.addView(it) }
-                    }
                 }
                 ClockSize.SMALL -> {
                     clock.smallClock.layout.views.forEach { burnInLayer?.addView(it) }
-                    clock.largeClock.layout.views.forEach { burnInLayer?.removeView(it) }
                 }
             }
         }
@@ -148,13 +174,6 @@
                 burnInLayer?.removeView(it)
                 rootView.removeView(it)
             }
-
-            // add large clock to burn in layer only when it will have same transition with other
-            // components in AOD otherwise, it will have a separate scale transition while other
-            // components only have translate transition
-            if (clock.config.useAlternateSmartspaceAODTransition) {
-                clock.largeClock.layout.views.forEach { burnInLayer?.removeView(it) }
-            }
             clock.largeClock.layout.views.forEach { rootView.removeView(it) }
         }
         lastClock = currentClock
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index abd79ab..b9a79dc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -118,6 +118,7 @@
             }
 
             override fun destroy() {
+                view.setOnApplyWindowInsetsListener(null)
                 disposableHandle.dispose()
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index ccc48b5..39db22d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -36,7 +36,6 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.animation.Interpolators
-import com.android.app.tracing.coroutines.launch
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
 import com.android.systemui.Flags.newAodTransition
@@ -237,7 +236,8 @@
                                                 indicationArea,
                                                 startButton,
                                                 endButton,
-                                                lockIcon -> {
+                                                lockIcon,
+                                                deviceEntryIcon -> {
                                                     // Do not move these views
                                                 }
                                                 else -> childView.translationX = px
@@ -257,23 +257,6 @@
                                         it.scaleX = scaleViewModel.scale
                                         it.scaleY = scaleViewModel.scale
                                     }
-                                    // Make sure to reset these views, or they will be invisible
-                                    if (childViews[burnInLayerId]?.scaleX != 1f) {
-                                        childViews[burnInLayerId]?.scaleX = 1f
-                                        childViews[burnInLayerId]?.scaleY = 1f
-                                        childViews[aodNotificationIconContainerId]?.scaleX = 1f
-                                        childViews[aodNotificationIconContainerId]?.scaleY = 1f
-                                        view.requestLayout()
-                                    }
-                                } else {
-                                    // For weather clock, large clock should have only scale
-                                    // transition with other parts in burnInLayer
-                                    childViews[burnInLayerId]?.scaleX = scaleViewModel.scale
-                                    childViews[burnInLayerId]?.scaleY = scaleViewModel.scale
-                                    childViews[aodNotificationIconContainerId]?.scaleX =
-                                        scaleViewModel.scale
-                                    childViews[aodNotificationIconContainerId]?.scaleY =
-                                        scaleViewModel.scale
                                 }
                             }
                         }
@@ -597,6 +580,7 @@
     private val startButton = R.id.start_button
     private val endButton = R.id.end_button
     private val lockIcon = R.id.lock_icon_view
+    private val deviceEntryIcon = R.id.device_entry_icon_view
     private val nsslPlaceholderId = R.id.nssl_placeholder
 
     private const val ID = "occluding_app_device_entry_unlock_msg"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
index a8e9041..1f4bc61 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
@@ -29,7 +29,10 @@
 import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DozingToPrimaryBouncerTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToAodTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.GoneToDozingTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.GoneToLockscreenTransitionViewModel
@@ -40,7 +43,9 @@
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToPrimaryBouncerTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludedToAodTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.OccludedToGlanceableHubTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.OffToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToAodTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToDozingTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel
@@ -196,6 +201,12 @@
 
     @Binds
     @IntoSet
+    abstract fun offToLockscreen(
+        impl: OffToLockscreenTransitionViewModel
+    ): DeviceEntryIconTransition
+
+    @Binds
+    @IntoSet
     abstract fun primaryBouncerToAod(
         impl: PrimaryBouncerToAodTransitionViewModel
     ): DeviceEntryIconTransition
@@ -211,4 +222,28 @@
     abstract fun primaryBouncerToLockscreen(
         impl: PrimaryBouncerToLockscreenTransitionViewModel
     ): DeviceEntryIconTransition
+
+    @Binds
+    @IntoSet
+    abstract fun dreamingToGlanceableHub(
+        impl: DreamingToGlanceableHubTransitionViewModel
+    ): DeviceEntryIconTransition
+
+    @Binds
+    @IntoSet
+    abstract fun glanceableHubToDreaming(
+        impl: GlanceableHubToDreamingTransitionViewModel
+    ): DeviceEntryIconTransition
+
+    @Binds
+    @IntoSet
+    abstract fun glanceableHubToOccluded(
+        impl: GlanceableHubToOccludedTransitionViewModel
+    ): DeviceEntryIconTransition
+
+    @Binds
+    @IntoSet
+    abstract fun occludedToGlanceableHub(
+        impl: OccludedToGlanceableHubTransitionViewModel
+    ): DeviceEntryIconTransition
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
index 35b2598..200d30c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
@@ -65,12 +65,12 @@
             object : AccessibilityDelegate() {
                 private val accessibilityAuthenticateHint =
                     AccessibilityNodeInfo.AccessibilityAction(
-                        AccessibilityNodeInfoCompat.ACTION_LONG_CLICK,
+                        AccessibilityNodeInfoCompat.ACTION_CLICK,
                         resources.getString(R.string.accessibility_authenticate_hint)
                     )
                 private val accessibilityEnterHint =
                     AccessibilityNodeInfo.AccessibilityAction(
-                        AccessibilityNodeInfoCompat.ACTION_LONG_CLICK,
+                        AccessibilityNodeInfoCompat.ACTION_CLICK,
                         resources.getString(R.string.accessibility_enter_hint)
                     )
                 override fun onInitializeAccessibilityNodeInfo(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index ef29270..b367715 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.binder.KeyguardClockViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockFaceLayout
@@ -64,6 +65,7 @@
     private val context: Context,
     val smartspaceViewModel: KeyguardSmartspaceViewModel,
     val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
+    private val rootViewModel: KeyguardRootViewModel,
 ) : KeyguardSection() {
     override fun addViews(constraintLayout: ConstraintLayout) {}
     override fun bindData(constraintLayout: ConstraintLayout) {
@@ -75,7 +77,8 @@
             constraintLayout,
             keyguardClockViewModel,
             clockInteractor,
-            blueprintInteractor.get()
+            blueprintInteractor.get(),
+            rootViewModel,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index 45b8257..9146c60 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.ui.view.layout.sections
 
 import android.content.res.Resources
+import android.view.WindowInsets
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
@@ -25,15 +26,19 @@
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.RIGHT
 import androidx.constraintlayout.widget.ConstraintSet.VISIBILITY_MODE_IGNORE
+import com.android.systemui.animation.view.LaunchableImageView
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
 import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.KeyguardIndicationController
 import com.android.systemui.statusbar.VibratorHelper
+import dagger.Lazy
 import javax.inject.Inject
 
 class DefaultShortcutsSection
@@ -46,11 +51,29 @@
     private val falsingManager: FalsingManager,
     private val indicationController: KeyguardIndicationController,
     private val vibratorHelper: VibratorHelper,
+    private val keyguardBlueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
 ) : BaseShortcutSection() {
+
+    // Amount to increase the bottom margin by to avoid colliding with inset
+    private var safeInsetBottom = 0
+
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (KeyguardBottomAreaRefactor.isEnabled) {
             addLeftShortcut(constraintLayout)
             addRightShortcut(constraintLayout)
+
+            constraintLayout
+                .requireViewById<LaunchableImageView>(R.id.start_button)
+                .setOnApplyWindowInsetsListener { _, windowInsets ->
+                    val tempSafeInset = windowInsets?.displayCutout?.safeInsetBottom ?: 0
+                    if (safeInsetBottom != tempSafeInset) {
+                        safeInsetBottom = tempSafeInset
+                        keyguardBlueprintInteractor
+                            .get()
+                            .refreshBlueprint(IntraBlueprintTransition.Type.DefaultTransition)
+                    }
+                    WindowInsets.CONSUMED
+                }
         }
     }
 
@@ -91,12 +114,24 @@
             constrainWidth(R.id.start_button, width)
             constrainHeight(R.id.start_button, height)
             connect(R.id.start_button, LEFT, PARENT_ID, LEFT, horizontalOffsetMargin)
-            connect(R.id.start_button, BOTTOM, PARENT_ID, BOTTOM, verticalOffsetMargin)
+            connect(
+                R.id.start_button,
+                BOTTOM,
+                PARENT_ID,
+                BOTTOM,
+                verticalOffsetMargin + safeInsetBottom
+            )
 
             constrainWidth(R.id.end_button, width)
             constrainHeight(R.id.end_button, height)
             connect(R.id.end_button, RIGHT, PARENT_ID, RIGHT, horizontalOffsetMargin)
-            connect(R.id.end_button, BOTTOM, PARENT_ID, BOTTOM, verticalOffsetMargin)
+            connect(
+                R.id.end_button,
+                BOTTOM,
+                PARENT_ID,
+                BOTTOM,
+                verticalOffsetMargin + safeInsetBottom
+            )
 
             // The constraint set visibility for start and end button are default visible, set to
             // ignore so the view's own initial visibility (invisible) is used
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
index 7c29b39..7c745bc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
@@ -38,6 +38,8 @@
 import com.google.android.material.math.MathUtils
 import kotlin.math.abs
 
+internal fun View.getRect(): Rect = Rect(this.left, this.top, this.right, this.bottom)
+
 internal fun View.setRect(rect: Rect) =
     this.setLeftTopRightBottom(rect.left, rect.top, rect.right, rect.bottom)
 
@@ -55,9 +57,9 @@
         addTransition(SmartspaceMoveTransition(config, clockViewModel))
     }
 
-    open class VisibilityBoundsTransition() : Transition() {
-        var captureSmartspace: Boolean = false
-
+    abstract class VisibilityBoundsTransition() : Transition() {
+        abstract val captureSmartspace: Boolean
+        protected val TAG = this::class.simpleName!!
         override fun captureEndValues(transition: TransitionValues) = captureValues(transition)
         override fun captureStartValues(transition: TransitionValues) = captureValues(transition)
         override fun getTransitionProperties(): Array<String> = TRANSITION_PROPERTIES
@@ -66,12 +68,18 @@
             val view = transition.view
             transition.values[PROP_VISIBILITY] = view.visibility
             transition.values[PROP_ALPHA] = view.alpha
-            transition.values[PROP_BOUNDS] = Rect(view.left, view.top, view.right, view.bottom)
+            transition.values[PROP_BOUNDS] = view.getRect()
 
             if (!captureSmartspace) return
-            val ss = (view.parent as View).findViewById<View>(sharedR.id.bc_smartspace_view)
-            if (ss == null) return
-            transition.values[SMARTSPACE_BOUNDS] = Rect(ss.left, ss.top, ss.right, ss.bottom)
+            val parent = view.parent as View
+            val targetSSView =
+                parent.findViewById<View>(sharedR.id.bc_smartspace_view)
+                    ?: parent.findViewById<View>(R.id.keyguard_slice_view)
+            if (targetSSView == null) {
+                Log.e(TAG, "Failed to find smartspace equivalent target under $parent")
+                return
+            }
+            transition.values[SMARTSPACE_BOUNDS] = targetSSView.getRect()
         }
 
         open fun mutateBounds(
@@ -89,20 +97,24 @@
             startValues: TransitionValues?,
             endValues: TransitionValues?
         ): Animator? {
-            if (startValues == null || endValues == null) return null
+            if (startValues == null || endValues == null) {
+                Log.w(
+                    TAG,
+                    "Couldn't create animator: startValues=$startValues; endValues=$endValues"
+                )
+                return null
+            }
 
             var fromVis = startValues.values[PROP_VISIBILITY] as Int
             var fromIsVis = fromVis == View.VISIBLE
             var fromAlpha = startValues.values[PROP_ALPHA] as Float
             val fromBounds = startValues.values[PROP_BOUNDS] as Rect
-            val fromSSBounds =
-                if (captureSmartspace) startValues.values[SMARTSPACE_BOUNDS] as Rect else null
+            val fromSSBounds = startValues.values[SMARTSPACE_BOUNDS] as Rect?
 
             val toView = endValues.view
             val toVis = endValues.values[PROP_VISIBILITY] as Int
             val toBounds = endValues.values[PROP_BOUNDS] as Rect
-            val toSSBounds =
-                if (captureSmartspace) endValues.values[SMARTSPACE_BOUNDS] as Rect else null
+            val toSSBounds = endValues.values[SMARTSPACE_BOUNDS] as Rect?
             val toIsVis = toVis == View.VISIBLE
             val toAlpha = if (toIsVis) 1f else 0f
 
@@ -141,11 +153,12 @@
             fun assignAnimValues(src: String, fract: Float, vis: Int? = null) {
                 val bounds = computeBounds(fract)
                 val alpha = MathUtils.lerp(fromAlpha, toAlpha, fract)
-                if (DEBUG)
+                if (DEBUG) {
                     Log.i(
                         TAG,
                         "$src: $toView; fract=$fract; alpha=$alpha; vis=$vis; bounds=$bounds;"
                     )
+                }
                 toView.setVisibility(vis ?: View.VISIBLE)
                 toView.setAlpha(alpha)
                 toView.setRect(bounds)
@@ -206,9 +219,6 @@
             private const val SMARTSPACE_BOUNDS = "ClockSizeTransition:SSBounds"
             private val TRANSITION_PROPERTIES =
                 arrayOf(PROP_VISIBILITY, PROP_ALPHA, PROP_BOUNDS, SMARTSPACE_BOUNDS)
-
-            private val DEBUG = false
-            private val TAG = VisibilityBoundsTransition::class.simpleName!!
         }
     }
 
@@ -217,18 +227,24 @@
         val viewModel: KeyguardClockViewModel,
         val smartspaceViewModel: KeyguardSmartspaceViewModel,
     ) : VisibilityBoundsTransition() {
+        override val captureSmartspace = !viewModel.isLargeClockVisible.value
+
         init {
             duration = CLOCK_IN_MILLIS
             startDelay = CLOCK_IN_START_DELAY_MILLIS
             interpolator = CLOCK_IN_INTERPOLATOR
-            captureSmartspace =
-                !viewModel.isLargeClockVisible.value && smartspaceViewModel.isSmartspaceEnabled
 
             if (viewModel.isLargeClockVisible.value) {
                 viewModel.currentClock.value?.let {
+                    if (DEBUG) Log.i(TAG, "Large Clock In: ${it.largeClock.layout.views}")
                     it.largeClock.layout.views.forEach { addTarget(it) }
                 }
+                    ?: run {
+                        Log.e(TAG, "No large clock set, falling back")
+                        addTarget(R.id.lockscreen_clock_view_large)
+                    }
             } else {
+                if (DEBUG) Log.i(TAG, "Small Clock In")
                 addTarget(R.id.lockscreen_clock_view)
             }
         }
@@ -245,12 +261,9 @@
             // Move normally if clock is not changing visibility
             if (fromIsVis == toIsVis) return
 
-            fromBounds.left = toBounds.left
-            fromBounds.right = toBounds.right
+            fromBounds.set(toBounds)
             if (viewModel.isLargeClockVisible.value) {
-                // Large clock shouldn't move
-                fromBounds.top = toBounds.top
-                fromBounds.bottom = toBounds.bottom
+                // Large clock shouldn't move; fromBounds already set
             } else if (toSSBounds != null && fromSSBounds != null) {
                 // Instead of moving the small clock the full distance, we compute the distance
                 // smartspace will move. We then scale this to match the duration of this animation
@@ -270,7 +283,6 @@
             val CLOCK_IN_INTERPOLATOR = Interpolators.LINEAR_OUT_SLOW_IN
             const val SMALL_CLOCK_IN_MOVE_SCALE =
                 CLOCK_IN_MILLIS / SmartspaceMoveTransition.STATUS_AREA_MOVE_DOWN_MILLIS.toFloat()
-            private val TAG = ClockFaceInTransition::class.simpleName!!
         }
     }
 
@@ -279,18 +291,24 @@
         val viewModel: KeyguardClockViewModel,
         val smartspaceViewModel: KeyguardSmartspaceViewModel,
     ) : VisibilityBoundsTransition() {
+        override val captureSmartspace = viewModel.isLargeClockVisible.value
+
         init {
             duration = CLOCK_OUT_MILLIS
             interpolator = CLOCK_OUT_INTERPOLATOR
-            captureSmartspace =
-                viewModel.isLargeClockVisible.value && smartspaceViewModel.isSmartspaceEnabled
 
             if (viewModel.isLargeClockVisible.value) {
+                if (DEBUG) Log.i(TAG, "Small Clock Out")
                 addTarget(R.id.lockscreen_clock_view)
             } else {
                 viewModel.currentClock.value?.let {
+                    if (DEBUG) Log.i(TAG, "Large Clock Out: ${it.largeClock.layout.views}")
                     it.largeClock.layout.views.forEach { addTarget(it) }
                 }
+                    ?: run {
+                        Log.e(TAG, "No large clock set, falling back")
+                        addTarget(R.id.lockscreen_clock_view_large)
+                    }
             }
         }
 
@@ -306,12 +324,9 @@
             // Move normally if clock is not changing visibility
             if (fromIsVis == toIsVis) return
 
-            toBounds.left = fromBounds.left
-            toBounds.right = fromBounds.right
+            toBounds.set(fromBounds)
             if (!viewModel.isLargeClockVisible.value) {
-                // Large clock shouldn't move
-                toBounds.top = fromBounds.top
-                toBounds.bottom = fromBounds.bottom
+                // Large clock shouldn't move; toBounds already set
             } else if (toSSBounds != null && fromSSBounds != null) {
                 // Instead of moving the small clock the full distance, we compute the distance
                 // smartspace will move. We then scale this to match the duration of this animation
@@ -321,7 +336,7 @@
                 toBounds.top = fromBounds.top - ssTranslation
                 toBounds.bottom = fromBounds.bottom - ssTranslation
             } else {
-                Log.w(TAG, "mutateBounds: smallClock received no smartspace bounds")
+                Log.e(TAG, "mutateBounds: smallClock received no smartspace bounds")
             }
         }
 
@@ -330,7 +345,6 @@
             val CLOCK_OUT_INTERPOLATOR = Interpolators.LINEAR
             const val SMALL_CLOCK_OUT_MOVE_SCALE =
                 CLOCK_OUT_MILLIS / SmartspaceMoveTransition.STATUS_AREA_MOVE_UP_MILLIS.toFloat()
-            private val TAG = ClockFaceOutTransition::class.simpleName!!
         }
     }
 
@@ -339,6 +353,8 @@
         val config: IntraBlueprintTransition.Config,
         viewModel: KeyguardClockViewModel,
     ) : VisibilityBoundsTransition() {
+        override val captureSmartspace = false
+
         init {
             duration =
                 if (viewModel.isLargeClockVisible.value) STATUS_AREA_MOVE_UP_MILLIS
@@ -358,4 +374,8 @@
             const val STATUS_AREA_MOVE_DOWN_MILLIS = 467L
         }
     }
+
+    companion object {
+        val DEBUG = true
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
index 4fd92d7..9da11ce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
@@ -19,7 +19,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
@@ -42,8 +44,7 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = FromAlternateBouncerTransitionInteractor.TO_AOD_DURATION,
-            from = KeyguardState.ALTERNATE_BOUNCER,
-            to = KeyguardState.AOD,
+            edge = Edge.create(from = ALTERNATE_BOUNCER, to = AOD),
         )
 
     val deviceEntryBackgroundViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToDozingTransitionViewModel.kt
index 9649af73..55a48b6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToDozingTransitionViewModel.kt
@@ -19,7 +19,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
@@ -42,8 +44,7 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = FromAlternateBouncerTransitionInteractor.TO_DOZING_DURATION,
-            from = KeyguardState.ALTERNATE_BOUNCER,
-            to = KeyguardState.DOZING,
+            edge = Edge.create(from = ALTERNATE_BOUNCER, to = DOZING),
         )
 
     val deviceEntryBackgroundViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
index 8c6be98..bb4fb79 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
@@ -19,11 +19,13 @@
 import android.util.MathUtils
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TO_GONE_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.ScrimAlpha
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -44,11 +46,14 @@
     private val statusBarStateController: SysuiStatusBarStateController,
 ) : DeviceEntryIconTransition {
     private val transitionAnimation =
-        animationFlow.setup(
-            duration = TO_GONE_DURATION,
-            from = ALTERNATE_BOUNCER,
-            to = KeyguardState.GONE,
-        )
+        animationFlow
+            .setup(
+                duration = TO_GONE_DURATION,
+                edge = Edge.create(from = ALTERNATE_BOUNCER, to = Scenes.Gone),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = ALTERNATE_BOUNCER, to = GONE),
+            )
 
     fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
         var startAlpha = 1f
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt
index 27febd3..3f2ef29 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt
@@ -18,8 +18,9 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TO_OCCLUDED_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
@@ -40,8 +41,7 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_OCCLUDED_DURATION,
-            from = ALTERNATE_BOUNCER,
-            to = KeyguardState.OCCLUDED,
+            edge = Edge.create(from = ALTERNATE_BOUNCER, to = OCCLUDED),
         )
 
     override val deviceEntryParentViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
index 7592881..f0bccac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
@@ -18,9 +18,12 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -37,11 +40,14 @@
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
     private val transitionAnimation =
-        animationFlow.setup(
-            duration = FromAlternateBouncerTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
-            from = KeyguardState.ALTERNATE_BOUNCER,
-            to = KeyguardState.PRIMARY_BOUNCER,
-        )
+        animationFlow
+            .setup(
+                duration = FromAlternateBouncerTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
+                edge = Edge.create(from = ALTERNATE_BOUNCER, to = Scenes.Bouncer),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = ALTERNATE_BOUNCER, to = PRIMARY_BOUNCER),
+            )
 
     override val deviceEntryParentViewAlpha: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt
index 5cf100e..4128c52 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt
@@ -34,8 +34,8 @@
     alternateBouncerInteractor: AlternateBouncerInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
 ) {
-    private val deviceSupportsAlternateBouncer: Flow<Boolean> =
-        alternateBouncerInteractor.alternateBouncerSupported
+    val canShowAlternateBouncer: Flow<Boolean> = alternateBouncerInteractor.canShowAlternateBouncer
+
     private val isTransitioningToOrFromOrShowingAlternateBouncer: Flow<Boolean> =
         keyguardTransitionInteractor
             .transitionValue(KeyguardState.ALTERNATE_BOUNCER)
@@ -43,8 +43,8 @@
             .distinctUntilChanged()
 
     val alternateBouncerWindowRequired: Flow<Boolean> =
-        deviceSupportsAlternateBouncer.flatMapLatest { deviceSupportsAlternateBouncer ->
-            if (deviceSupportsAlternateBouncer) {
+        canShowAlternateBouncer.flatMapLatest { canShowAlternateBouncer ->
+            if (canShowAlternateBouncer) {
                 isTransitioningToOrFromOrShowingAlternateBouncer
             } else {
                 flowOf(false)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
index 5b83a10..c05a1b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.ComposeLockscreen
 import com.android.systemui.keyguard.shared.model.BurnInModel
 import com.android.systemui.keyguard.shared.model.ClockSize
 import com.android.systemui.keyguard.ui.StateToValue
@@ -121,11 +120,13 @@
             ),
         ) { interpolated, burnIn ->
             val useAltAod =
-                keyguardClockViewModel.currentClock.value?.let { clock ->
-                    clock.config.useAlternateSmartspaceAODTransition
-                } == true
+                keyguardClockViewModel.currentClock.value
+                    ?.config
+                    ?.useAlternateSmartspaceAODTransition == true
+            // Only scale large non-weather clocks
+            // elements in large weather clock will translate the same as smartspace
             val useScaleOnly =
-                useAltAod && keyguardClockViewModel.clockSize.value == ClockSize.LARGE
+                (!useAltAod) && keyguardClockViewModel.clockSize.value == ClockSize.LARGE
 
             val burnInY = MathUtils.lerp(0, burnIn.translationY, interpolated).toInt()
             val translationY =
@@ -134,35 +135,12 @@
                 } else {
                     max(params.topInset, params.minViewY + burnInY) - params.minViewY
                 }
-            if (ComposeLockscreen.isEnabled) {
-                BurnInModel(
-                    translationX = MathUtils.lerp(0, burnIn.translationX, interpolated).toInt(),
-                    translationY = translationY,
-                    scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolated),
-                    scaleClockOnly = !useScaleOnly,
-                )
-            } else {
-                if (useScaleOnly) {
-                    BurnInModel(
-                        translationX = 0,
-                        translationY = 0,
-                        scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolated),
-                    )
-                } else {
-                    // Ensure the desired translation doesn't encroach on the top inset
-                    BurnInModel(
-                        translationX = MathUtils.lerp(0, burnIn.translationX, interpolated).toInt(),
-                        translationY = translationY,
-                        scale =
-                            MathUtils.lerp(
-                                /* start= */ burnIn.scale,
-                                /* stop= */ 1f,
-                                /* amount= */ 1f - interpolated,
-                            ),
-                        scaleClockOnly = true,
-                    )
-                }
-            }
+            BurnInModel(
+                translationX = MathUtils.lerp(0, burnIn.translationX, interpolated).toInt(),
+                translationY = translationY,
+                scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolated),
+                scaleClockOnly = useScaleOnly
+            )
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
index adc090d..8e8b09d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
@@ -19,9 +19,12 @@
 import android.util.MathUtils
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -37,11 +40,14 @@
 ) : DeviceEntryIconTransition {
 
     private val transitionAnimation =
-        animationFlow.setup(
-            duration = FromAodTransitionInteractor.TO_GONE_DURATION,
-            from = KeyguardState.AOD,
-            to = KeyguardState.GONE,
-        )
+        animationFlow
+            .setup(
+                duration = FromAodTransitionInteractor.TO_GONE_DURATION,
+                edge = Edge.create(from = AOD, to = Scenes.Gone),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = AOD, to = GONE),
+            )
 
     /**
      * AOD -> GONE should fade out the lockscreen contents. This transition plays both during wake
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
index cbbb820..b267ecb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
@@ -19,9 +19,10 @@
 import android.util.MathUtils
 import com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.StateToValue
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
@@ -39,7 +40,6 @@
 class AodToLockscreenTransitionViewModel
 @Inject
 constructor(
-    deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
     shadeInteractor: ShadeInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
@@ -47,8 +47,7 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_LOCKSCREEN_DURATION,
-            from = KeyguardState.AOD,
-            to = KeyguardState.LOCKSCREEN,
+            edge = Edge.create(from = AOD, to = LOCKSCREEN),
         )
 
     private var isShadeExpanded = false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
index 445575f..2497def 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
@@ -19,7 +19,9 @@
 import android.util.MathUtils
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
@@ -36,8 +38,7 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = FromAodTransitionInteractor.TO_OCCLUDED_DURATION,
-            from = KeyguardState.AOD,
-            to = KeyguardState.OCCLUDED,
+            edge = Edge.create(from = AOD, to = OCCLUDED),
         )
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
index 9a23007..35f05f5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
@@ -18,9 +18,12 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -37,11 +40,14 @@
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
     private val transitionAnimation =
-        animationFlow.setup(
-            duration = FromAodTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
-            from = KeyguardState.AOD,
-            to = KeyguardState.PRIMARY_BOUNCER,
-        )
+        animationFlow
+            .setup(
+                duration = FromAodTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
+                edge = Edge.create(from = AOD, to = Scenes.Bouncer),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = AOD, to = PRIMARY_BOUNCER),
+            )
 
     override val deviceEntryParentViewAlpha: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
index 570f377..caa0436 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
@@ -19,11 +19,13 @@
 import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.ScrimAlpha
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import dagger.Lazy
@@ -73,8 +75,12 @@
         return animationFlow
             .setup(
                 duration = duration,
-                from = from,
-                to = GONE,
+                // TODO(b/330311871): from can be PRIMARY_BOUNCER which would be a scene -> scene
+                //  transition
+                edge = Edge.create(from = from, to = Scenes.Gone)
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = from, to = GONE),
             )
             .sharedFlow(
                 duration = duration,
@@ -96,11 +102,16 @@
         var leaveShadeOpen: Boolean = false
         var willRunDismissFromKeyguard: Boolean = false
         val transitionAnimation =
-            animationFlow.setup(
-                duration = duration,
-                from = fromState,
-                to = GONE,
-            )
+            animationFlow
+                .setup(
+                    duration = duration,
+                    // TODO(b/330311871): from can be PRIMARY_BOUNCER which would be a scene ->
+                    //  scene transition
+                    edge = Edge.create(from = fromState, to = Scenes.Gone),
+                )
+                .setupWithoutSceneContainer(
+                    edge = Edge.create(from = fromState, to = GONE),
+                )
 
         return shadeInteractor.anyExpansion
             .map { it > 0f }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index 87324a2..6f8389f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -117,7 +117,8 @@
                             KeyguardState.DOZING,
                             KeyguardState.DREAMING,
                             KeyguardState.PRIMARY_BOUNCER,
-                            KeyguardState.AOD -> emit(0f)
+                            KeyguardState.AOD,
+                            KeyguardState.UNDEFINED -> emit(0f)
                             KeyguardState.ALTERNATE_BOUNCER,
                             KeyguardState.LOCKSCREEN -> emit(1f)
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 40be73e..ae83c9e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -19,6 +19,7 @@
 import android.animation.FloatEvaluator
 import android.animation.IntEvaluator
 import com.android.keyguard.KeyguardViewController
+import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
@@ -68,6 +69,7 @@
     private val keyguardViewController: Lazy<KeyguardViewController>,
     private val deviceEntryInteractor: DeviceEntryInteractor,
     private val deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
+    private val accessibilityInteractor: AccessibilityInteractor,
     @Application private val scope: CoroutineScope,
 ) {
     val isUdfpsSupported: StateFlow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
@@ -84,19 +86,21 @@
             .map { it.deviceEntryParentViewAlpha }
             .merge()
             .shareIn(scope, SharingStarted.WhileSubscribed())
+            .onStart { emit(initialAlphaFromKeyguardState(transitionInteractor.getCurrentState())) }
     private val alphaMultiplierFromShadeExpansion: Flow<Float> =
         combine(
-            showingAlternateBouncer,
-            shadeExpansion,
-            qsProgress,
-        ) { showingAltBouncer, shadeExpansion, qsProgress ->
-            val interpolatedQsProgress = (qsProgress * 2).coerceIn(0f, 1f)
-            if (showingAltBouncer) {
-                1f
-            } else {
-                (1f - shadeExpansion) * (1f - interpolatedQsProgress)
+                showingAlternateBouncer,
+                shadeExpansion,
+                qsProgress,
+            ) { showingAltBouncer, shadeExpansion, qsProgress ->
+                val interpolatedQsProgress = (qsProgress * 2).coerceIn(0f, 1f)
+                if (showingAltBouncer) {
+                    1f
+                } else {
+                    (1f - shadeExpansion) * (1f - interpolatedQsProgress)
+                }
             }
-        }
+            .onStart { emit(1f) }
     // Burn-in offsets in AOD
     private val nonAnimatedBurnInOffsets: Flow<BurnInOffsets> =
         combine(
@@ -122,14 +126,35 @@
             )
         }
 
-    val deviceEntryViewAlpha: StateFlow<Float> =
+    val deviceEntryViewAlpha: Flow<Float> =
         combine(
                 transitionAlpha,
                 alphaMultiplierFromShadeExpansion,
             ) { alpha, alphaMultiplier ->
                 alpha * alphaMultiplier
             }
-            .stateIn(scope = scope, started = SharingStarted.WhileSubscribed(), initialValue = 0f)
+            .stateIn(
+                scope = scope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = 0f,
+            )
+
+    private fun initialAlphaFromKeyguardState(keyguardState: KeyguardState): Float {
+        return when (keyguardState) {
+            KeyguardState.OFF,
+            KeyguardState.PRIMARY_BOUNCER,
+            KeyguardState.DOZING,
+            KeyguardState.DREAMING,
+            KeyguardState.GLANCEABLE_HUB,
+            KeyguardState.GONE,
+            KeyguardState.OCCLUDED,
+            KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+            KeyguardState.UNDEFINED, -> 0f
+            KeyguardState.AOD,
+            KeyguardState.ALTERNATE_BOUNCER,
+            KeyguardState.LOCKSCREEN, -> 1f
+        }
+    }
     val useBackgroundProtection: StateFlow<Boolean> = isUdfpsSupported
     val burnInOffsets: Flow<BurnInOffsets> =
         deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled
@@ -210,7 +235,8 @@
             }
         }
     val isVisible: Flow<Boolean> = deviceEntryViewAlpha.map { it > 0f }.distinctUntilChanged()
-    val isLongPressEnabled: Flow<Boolean> =
+
+    private val isInteractive: Flow<Boolean> =
         combine(
             iconType,
             isUdfpsSupported,
@@ -222,17 +248,24 @@
                 DeviceEntryIconView.IconType.NONE -> false
             }
         }
-
     val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> =
-        combine(iconType, isLongPressEnabled) { deviceEntryStatus, longPressEnabled ->
-            if (longPressEnabled) {
-                deviceEntryStatus.toAccessibilityHintType()
+        accessibilityInteractor.isEnabled.flatMapLatest { touchExplorationEnabled ->
+            if (touchExplorationEnabled) {
+                combine(iconType, isInteractive) { iconType, isInteractive ->
+                    if (isInteractive) {
+                        iconType.toAccessibilityHintType()
+                    } else {
+                        DeviceEntryIconView.AccessibilityHintType.NONE
+                    }
+                }
             } else {
-                DeviceEntryIconView.AccessibilityHintType.NONE
+                flowOf(DeviceEntryIconView.AccessibilityHintType.NONE)
             }
         }
 
-    suspend fun onLongPress() {
+    val isLongPressEnabled: Flow<Boolean> = isInteractive
+
+    suspend fun onUserInteraction() {
         if (SceneContainerFlag.isEnabled) {
             deviceEntryInteractor.attemptDeviceEntry()
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt
index 8851a51..77ebfce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt
@@ -19,9 +19,12 @@
 import android.util.MathUtils
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_GONE_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -37,11 +40,14 @@
 ) : DeviceEntryIconTransition {
 
     private val transitionAnimation =
-        animationFlow.setup(
-            duration = TO_GONE_DURATION,
-            from = KeyguardState.DOZING,
-            to = KeyguardState.GONE,
-        )
+        animationFlow
+            .setup(
+                duration = TO_GONE_DURATION,
+                edge = Edge.create(from = DOZING, to = Scenes.Gone),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = DOZING, to = GONE),
+            )
 
     fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
         var startAlpha = 1f
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt
index 168d6e1..a460d51 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt
@@ -18,7 +18,9 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
@@ -39,8 +41,7 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = FromDozingTransitionInteractor.TO_LOCKSCREEN_DURATION,
-            from = KeyguardState.DOZING,
-            to = KeyguardState.LOCKSCREEN,
+            edge = Edge.create(from = DOZING, to = LOCKSCREEN),
         )
 
     val shortcutsAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
index c0b1195..f33752f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
@@ -19,7 +19,9 @@
 import android.util.MathUtils
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
@@ -38,8 +40,7 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = FromAodTransitionInteractor.TO_OCCLUDED_DURATION,
-            from = KeyguardState.DOZING,
-            to = KeyguardState.OCCLUDED,
+            edge = Edge.create(from = DOZING, to = OCCLUDED),
         )
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
index 4395c34..7ddf641 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
@@ -18,9 +18,12 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_PRIMARY_BOUNCER_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -38,11 +41,14 @@
 ) : DeviceEntryIconTransition {
 
     private val transitionAnimation =
-        animationFlow.setup(
-            duration = TO_PRIMARY_BOUNCER_DURATION,
-            from = KeyguardState.DOZING,
-            to = KeyguardState.PRIMARY_BOUNCER,
-        )
+        animationFlow
+            .setup(
+                duration = TO_PRIMARY_BOUNCER_DURATION,
+                edge = Edge.create(from = DOZING, to = Scenes.Bouncer),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = DOZING, to = PRIMARY_BOUNCER),
+            )
 
     override val deviceEntryParentViewAlpha: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt
index 67568e1..57ed455 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt
@@ -18,7 +18,9 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromDreamingLockscreenHostedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -34,8 +36,7 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_LOCKSCREEN_DURATION,
-            from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
-            to = KeyguardState.LOCKSCREEN,
+            edge = Edge.create(from = DREAMING_LOCKSCREEN_HOSTED, to = LOCKSCREEN),
         )
 
     val shortcutsAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt
index 0fa7475..754ed6c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt
@@ -19,7 +19,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
@@ -40,12 +42,12 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = FromDreamingTransitionInteractor.TO_AOD_DURATION,
-            from = KeyguardState.DREAMING,
-            to = KeyguardState.AOD,
+            edge = Edge.create(from = DREAMING, to = AOD),
         )
 
     val deviceEntryBackgroundViewAlpha: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(0f)
+
     override val deviceEntryParentViewAlpha: Flow<Float> =
         deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolledAndEnabled
             ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
index 7468675..00aa102 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
@@ -19,9 +19,13 @@
 import com.android.app.animation.Interpolators.EMPHASIZED
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.Duration.Companion.seconds
@@ -37,13 +41,16 @@
 constructor(
     animationFlow: KeyguardTransitionAnimationFlow,
     configurationInteractor: ConfigurationInteractor,
-) {
+) : DeviceEntryIconTransition {
     private val transitionAnimation =
-        animationFlow.setup(
-            duration = TO_GLANCEABLE_HUB_DURATION,
-            from = KeyguardState.DREAMING,
-            to = KeyguardState.GLANCEABLE_HUB,
-        )
+        animationFlow
+            .setup(
+                duration = TO_GLANCEABLE_HUB_DURATION,
+                edge = Edge.create(from = DREAMING, to = Scenes.Communal),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = DREAMING, to = GLANCEABLE_HUB),
+            )
 
     val dreamOverlayTranslationX: Flow<Float> =
         configurationInteractor
@@ -79,6 +86,15 @@
             )
             .map { step -> step != 0f }
 
+    override val deviceEntryParentViewAlpha: Flow<Float> =
+        transitionAnimation.sharedFlow(
+            startTime = 167.milliseconds,
+            duration = 167.milliseconds,
+            onStep = { it },
+            onCancel = { 0f },
+            onFinish = { 1f },
+        )
+
     private companion object {
         val TO_GLANCEABLE_HUB_DURATION = 1.seconds
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModel.kt
index ec7b931..1bdf6d29 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModel.kt
@@ -18,10 +18,13 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
-import kotlinx.coroutines.flow.Flow
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
 
 @SysUISingleton
 class DreamingToGoneTransitionViewModel
@@ -31,13 +34,15 @@
 ) {
 
     private val transitionAnimation =
-            animationFlow.setup(
+        animationFlow
+            .setup(
                 duration = FromDreamingTransitionInteractor.TO_GONE_DURATION,
-                from = KeyguardState.DREAMING,
-                to = KeyguardState.GONE,
+                edge = Edge.create(from = DREAMING, to = Scenes.Gone),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = DREAMING, to = GONE),
             )
 
     /** Lockscreen views alpha */
     val lockscreenAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f)
-
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index f191aa7..82381eb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -20,7 +20,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
@@ -45,8 +47,7 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_LOCKSCREEN_DURATION,
-            from = KeyguardState.DREAMING,
-            to = KeyguardState.LOCKSCREEN,
+            edge = Edge.create(from = DREAMING, to = LOCKSCREEN),
         )
 
     /** Dream overlay y-translation on exit */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt
index 838c22b..d594488 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt
@@ -19,9 +19,13 @@
 import com.android.app.animation.Interpolators
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.Duration.Companion.seconds
@@ -37,14 +41,17 @@
 constructor(
     animationFlow: KeyguardTransitionAnimationFlow,
     configurationInteractor: ConfigurationInteractor,
-) {
+) : DeviceEntryIconTransition {
 
     private val transitionAnimation =
-        animationFlow.setup(
-            duration = FROM_GLANCEABLE_HUB_DURATION,
-            from = KeyguardState.GLANCEABLE_HUB,
-            to = KeyguardState.DREAMING,
-        )
+        animationFlow
+            .setup(
+                duration = FROM_GLANCEABLE_HUB_DURATION,
+                edge = Edge.create(from = Scenes.Communal, to = DREAMING),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = GLANCEABLE_HUB, to = DREAMING),
+            )
 
     val dreamOverlayAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
@@ -78,6 +85,14 @@
             )
             .map { step -> step != 1f }
 
+    override val deviceEntryParentViewAlpha: Flow<Float> =
+        transitionAnimation.sharedFlow(
+            duration = 167.milliseconds,
+            onStep = { 1 - it },
+            onCancel = { 1f },
+            onFinish = { 0f },
+        )
+
     private companion object {
         val FROM_GLANCEABLE_HUB_DURATION = 1.seconds
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
index e05b500..046b95f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -20,10 +20,13 @@
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.StateToValue
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,11 +48,14 @@
     animationFlow: KeyguardTransitionAnimationFlow,
 ) {
     private val transitionAnimation =
-        animationFlow.setup(
-            duration = TO_LOCKSCREEN_DURATION,
-            from = KeyguardState.GLANCEABLE_HUB,
-            to = KeyguardState.LOCKSCREEN,
-        )
+        animationFlow
+            .setup(
+                duration = TO_LOCKSCREEN_DURATION,
+                edge = Edge.create(from = Scenes.Communal, to = LOCKSCREEN),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = GLANCEABLE_HUB, to = LOCKSCREEN),
+            )
 
     val keyguardAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToOccludedTransitionViewModel.kt
new file mode 100644
index 0000000..cd98bb0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToOccludedTransitionViewModel.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor.Companion.TO_OCCLUDED_DURATION
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class GlanceableHubToOccludedTransitionViewModel
+@Inject
+constructor(
+    animationFlow: KeyguardTransitionAnimationFlow,
+) : DeviceEntryIconTransition {
+
+    private val transitionAnimation =
+        animationFlow
+            .setup(
+                duration = TO_OCCLUDED_DURATION,
+                edge = Edge.create(from = Scenes.Communal, to = OCCLUDED),
+            )
+            .setupWithoutSceneContainer(edge = Edge.create(from = GLANCEABLE_HUB, to = OCCLUDED))
+
+    override val deviceEntryParentViewAlpha: Flow<Float> =
+        transitionAnimation.immediatelyTransitionTo(0f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
index 3540bec..74f7d75 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
@@ -20,10 +20,13 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_AOD_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.StateToValue
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -42,11 +45,14 @@
 ) : DeviceEntryIconTransition {
 
     private val transitionAnimation =
-        animationFlow.setup(
-            duration = TO_AOD_DURATION,
-            from = KeyguardState.GONE,
-            to = KeyguardState.AOD,
-        )
+        animationFlow
+            .setup(
+                duration = TO_AOD_DURATION,
+                edge = Edge.create(from = Scenes.Gone, to = AOD),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = GONE, to = AOD),
+            )
 
     /** y-translation from the top of the screen for AOD */
     fun enterFromTopTranslationY(translatePx: Int): Flow<StateToValue> {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt
index 80a6bda..70c0032 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt
@@ -19,9 +19,12 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DOZING_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -40,11 +43,14 @@
 ) : DeviceEntryIconTransition {
 
     private val transitionAnimation =
-        animationFlow.setup(
-            duration = TO_DOZING_DURATION,
-            from = KeyguardState.GONE,
-            to = KeyguardState.DOZING,
-        )
+        animationFlow
+            .setup(
+                duration = TO_DOZING_DURATION,
+                edge = Edge.create(from = Scenes.Gone, to = DOZING),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = GONE, to = DOZING),
+            )
 
     val lockscreenAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt
index b527463..627f0de 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt
@@ -18,8 +18,11 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
@@ -36,11 +39,14 @@
 ) {
 
     private val transitionAnimation =
-        animationFlow.setup(
-            duration = TO_DREAMING_DURATION,
-            from = KeyguardState.GONE,
-            to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
-        )
+        animationFlow
+            .setup(
+                duration = TO_DREAMING_DURATION,
+                edge = Edge.create(from = Scenes.Gone, to = DREAMING_LOCKSCREEN_HOSTED),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = GONE, to = DREAMING_LOCKSCREEN_HOSTED),
+            )
 
     /** Lockscreen views alpha - hide immediately */
     val lockscreenAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
index 102242a..f8b6e28 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
@@ -19,8 +19,11 @@
 import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
@@ -34,11 +37,14 @@
 ) {
 
     private val transitionAnimation =
-        animationFlow.setup(
-            duration = TO_DREAMING_DURATION,
-            from = KeyguardState.GONE,
-            to = KeyguardState.DREAMING,
-        )
+        animationFlow
+            .setup(
+                duration = TO_DREAMING_DURATION,
+                edge = Edge.create(from = Scenes.Gone, to = DREAMING),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = GONE, to = DREAMING),
+            )
 
     /** Lockscreen views y-translation */
     fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt
index a2ce408..08ec43f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt
@@ -18,9 +18,12 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
@@ -33,11 +36,14 @@
 ) : DeviceEntryIconTransition {
 
     private val transitionAnimation =
-        animationFlow.setup(
-            duration = TO_LOCKSCREEN_DURATION,
-            from = KeyguardState.GONE,
-            to = KeyguardState.LOCKSCREEN
-        )
+        animationFlow
+            .setup(
+                duration = TO_LOCKSCREEN_DURATION,
+                edge = Edge.create(from = Scenes.Gone, to = LOCKSCREEN),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = GONE, to = LOCKSCREEN),
+            )
 
     val shortcutsAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt
similarity index 67%
rename from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt
index bf22563..e68e465 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt
@@ -16,10 +16,10 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
 import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
+class KeyguardMediaViewModel @Inject constructor(mediaCarouselInteractor: MediaCarouselInteractor) {
+    val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasActiveMediaOrRecommendation
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 24a7c51..f405b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -38,11 +39,12 @@
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.ui.StateToValue
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.util.kotlin.BooleanFlowOperators.or
+import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
 import com.android.systemui.util.kotlin.pairwise
 import com.android.systemui.util.kotlin.sample
 import com.android.systemui.util.ui.AnimatableEvent
@@ -64,7 +66,6 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.launch
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
@@ -116,14 +117,18 @@
     private val shadeInteractor: ShadeInteractor,
 ) {
     private var burnInJob: Job? = null
-    private val burnInModel = MutableStateFlow(BurnInModel())
+    internal val burnInModel = MutableStateFlow(BurnInModel())
 
     val burnInLayerVisibility: Flow<Int> =
         keyguardTransitionInteractor.startedKeyguardState
             .filter { it == AOD || it == LOCKSCREEN }
             .map { VISIBLE }
 
-    val goneToAodTransition = keyguardTransitionInteractor.transition(from = GONE, to = AOD)
+    val goneToAodTransition =
+        keyguardTransitionInteractor.transition(
+            edge = Edge.create(Scenes.Gone, AOD),
+            edgeWithoutSceneContainer = Edge.create(GONE, AOD)
+        )
 
     private val goneToAodTransitionRunning: Flow<Boolean> =
         goneToAodTransition
@@ -134,7 +139,7 @@
     private val isOnLockscreen: Flow<Boolean> =
         combine(
                 keyguardTransitionInteractor.isFinishedInState(LOCKSCREEN).onStart { emit(false) },
-                or(
+                anyOf(
                     keyguardTransitionInteractor.isInTransitionToState(LOCKSCREEN),
                     keyguardTransitionInteractor.isInTransitionFromState(LOCKSCREEN),
                 ),
@@ -145,7 +150,10 @@
 
     private val alphaOnShadeExpansion: Flow<Float> =
         combineTransform(
-                keyguardTransitionInteractor.isInTransition(from = LOCKSCREEN, to = GONE),
+                keyguardTransitionInteractor.isInTransition(
+                    edge = Edge.create(from = LOCKSCREEN, to = Scenes.Gone),
+                    edgeWithoutSceneContainer = Edge.create(from = LOCKSCREEN, to = GONE),
+                ),
                 isOnLockscreen,
                 shadeInteractor.qsExpansion,
                 shadeInteractor.shadeExpansion,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index d8b5013..02e48fc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
@@ -89,10 +90,15 @@
         shadeMode: ShadeMode,
     ): Map<UserAction, UserActionResult> {
         val shadeSceneKey =
-            if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade
+            UserActionResult(
+                toScene =
+                    if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade,
+                transitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split },
+            )
 
         val quickSettingsIfSingleShade =
-            if (shadeMode is ShadeMode.Single) Scenes.QuickSettings else shadeSceneKey
+            if (shadeMode is ShadeMode.Single) UserActionResult(Scenes.QuickSettings)
+            else shadeSceneKey
 
         return mapOf(
                 Swipe.Left to UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable },
@@ -103,7 +109,7 @@
                 swipeDownFromTop(pointerCount = 2) to
                     // TODO(b/338577208): Remove 'Dual' once we add Dual Shade invocation zones.
                     if (shadeMode is ShadeMode.Dual) {
-                        Scenes.QuickSettingsShade
+                        UserActionResult(Scenes.QuickSettingsShade)
                     } else {
                         quickSettingsIfSingleShade
                     },
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
index 1f9f304..8b5b347 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
@@ -20,7 +20,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
@@ -45,8 +47,7 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = FromLockscreenTransitionInteractor.TO_AOD_DURATION,
-            from = KeyguardState.LOCKSCREEN,
-            to = KeyguardState.AOD,
+            edge = Edge.create(from = LOCKSCREEN, to = AOD),
         )
 
     val deviceEntryBackgroundViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
index c836f01..27a1f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
@@ -19,7 +19,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DOZING_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
@@ -40,8 +42,7 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_DOZING_DURATION,
-            from = KeyguardState.LOCKSCREEN,
-            to = KeyguardState.DOZING,
+            edge = Edge.create(from = LOCKSCREEN, to = DOZING),
         )
 
     val lockscreenAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt
index 19b9cf47..778dbed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt
@@ -18,7 +18,9 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_HOSTED_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -34,8 +36,7 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_DREAMING_HOSTED_DURATION,
-            from = KeyguardState.LOCKSCREEN,
-            to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+            edge = Edge.create(from = LOCKSCREEN, to = DREAMING_LOCKSCREEN_HOSTED),
         )
 
     val shortcutsAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
index 13522a6..579abeb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
@@ -19,7 +19,9 @@
 import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
@@ -40,8 +42,7 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_DREAMING_DURATION,
-            from = KeyguardState.LOCKSCREEN,
-            to = KeyguardState.DREAMING,
+            edge = Edge.create(from = LOCKSCREEN, to = DREAMING),
         )
 
     /** Lockscreen views y-translation */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
index dae7897..c7273b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -20,10 +20,13 @@
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.StateToValue
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,11 +48,14 @@
     animationFlow: KeyguardTransitionAnimationFlow,
 ) {
     private val transitionAnimation =
-        animationFlow.setup(
-            duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
-            from = KeyguardState.LOCKSCREEN,
-            to = KeyguardState.GLANCEABLE_HUB,
-        )
+        animationFlow
+            .setup(
+                duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
+                edge = Edge.create(from = LOCKSCREEN, to = Scenes.Communal),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = LOCKSCREEN, to = GLANCEABLE_HUB),
+            )
 
     val keyguardAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
index f03625e..1314e88 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
@@ -19,10 +19,13 @@
 import android.util.MathUtils
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow.FlowBuilder
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -42,11 +45,14 @@
 ) : DeviceEntryIconTransition {
 
     private val transitionAnimation: FlowBuilder =
-        animationFlow.setup(
-            duration = FromLockscreenTransitionInteractor.TO_GONE_DURATION,
-            from = KeyguardState.LOCKSCREEN,
-            to = KeyguardState.GONE,
-        )
+        animationFlow
+            .setup(
+                duration = FromLockscreenTransitionInteractor.TO_GONE_DURATION,
+                edge = Edge.create(from = LOCKSCREEN, to = Scenes.Gone),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = LOCKSCREEN, to = GONE),
+            )
 
     val shortcutsAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
index dd6652e..fcf8c14f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
@@ -20,7 +20,9 @@
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import com.android.systemui.res.R
@@ -45,8 +47,7 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_OCCLUDED_DURATION,
-            from = KeyguardState.LOCKSCREEN,
-            to = KeyguardState.OCCLUDED,
+            edge = Edge.create(from = KeyguardState.LOCKSCREEN, to = OCCLUDED),
         )
 
     /** Lockscreen views alpha */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
index 0cfc757..23c44b0a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
@@ -18,9 +18,12 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -39,11 +42,14 @@
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
     private val transitionAnimation =
-        animationFlow.setup(
-            duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
-            from = KeyguardState.LOCKSCREEN,
-            to = KeyguardState.PRIMARY_BOUNCER,
-        )
+        animationFlow
+            .setup(
+                duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
+                edge = Edge.create(from = LOCKSCREEN, to = Scenes.Bouncer),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = LOCKSCREEN, to = PRIMARY_BOUNCER),
+            )
 
     val shortcutsAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt
index d7ba46b..706a3c4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt
@@ -19,7 +19,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
@@ -41,8 +43,7 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = FromOccludedTransitionInteractor.TO_AOD_DURATION,
-            from = KeyguardState.OCCLUDED,
-            to = KeyguardState.AOD,
+            edge = Edge.create(from = OCCLUDED, to = AOD),
         )
 
     val deviceEntryBackgroundViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt
index 91554e3..af01930 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt
@@ -18,7 +18,9 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -38,8 +40,7 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = FromOccludedTransitionInteractor.TO_DOZING_DURATION,
-            from = KeyguardState.OCCLUDED,
-            to = KeyguardState.DOZING,
+            edge = Edge.create(from = OCCLUDED, to = DOZING),
         )
 
     /** Lockscreen views alpha */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGlanceableHubTransitionViewModel.kt
new file mode 100644
index 0000000..47e202b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGlanceableHubTransitionViewModel.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_GLANCEABLE_HUB_DURATION
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class OccludedToGlanceableHubTransitionViewModel
+@Inject
+constructor(
+    animationFlow: KeyguardTransitionAnimationFlow,
+) : DeviceEntryIconTransition {
+
+    private val transitionAnimation =
+        animationFlow
+            .setup(
+                duration = TO_GLANCEABLE_HUB_DURATION,
+                edge = Edge.create(OCCLUDED, Scenes.Communal)
+            )
+            .setupWithoutSceneContainer(edge = Edge.create(OCCLUDED, GLANCEABLE_HUB))
+
+    override val deviceEntryParentViewAlpha: Flow<Float> =
+        transitionAnimation.immediatelyTransitionTo(1f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGoneTransitionViewModel.kt
index d2c9cfb..98dba39 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGoneTransitionViewModel.kt
@@ -17,8 +17,11 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -33,11 +36,14 @@
     animationFlow: KeyguardTransitionAnimationFlow,
 ) {
     private val transitionAnimation =
-        animationFlow.setup(
-            duration = DEFAULT_DURATION,
-            from = KeyguardState.OCCLUDED,
-            to = KeyguardState.GONE,
-        )
+        animationFlow
+            .setup(
+                duration = DEFAULT_DURATION,
+                edge = Edge.create(from = OCCLUDED, to = Scenes.Gone),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = OCCLUDED, to = GONE),
+            )
 
     fun notificationAlpha(viewState: ViewStateAccessor): Flow<Float> {
         var currentAlpha = 0f
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
index a09d58a..36c7d5b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
@@ -23,7 +23,9 @@
 import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import com.android.systemui.res.R
@@ -56,8 +58,7 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_LOCKSCREEN_DURATION,
-            from = KeyguardState.OCCLUDED,
-            to = KeyguardState.LOCKSCREEN,
+            edge = Edge.create(from = OCCLUDED, to = LOCKSCREEN),
         )
 
     /** Lockscreen views y-translation */
@@ -101,7 +102,7 @@
                 .filter { (wasOccluded, isOccluded) ->
                     wasOccluded &&
                         !isOccluded &&
-                        keyguardTransitionInteractor.getCurrentState() == KeyguardState.OCCLUDED
+                        keyguardTransitionInteractor.getCurrentState() == OCCLUDED
                 }
                 .map { 0f }
         )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
index 74094be..1eecbd5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
@@ -17,8 +17,11 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
@@ -28,13 +31,12 @@
 @Inject
 constructor(
     animationFlow: KeyguardTransitionAnimationFlow,
-) {
+) : DeviceEntryIconTransition {
 
     private val transitionAnimation =
         animationFlow.setup(
             duration = 250.milliseconds,
-            from = KeyguardState.OFF,
-            to = KeyguardState.LOCKSCREEN,
+            edge = Edge.create(from = OFF, to = LOCKSCREEN),
         )
 
     val shortcutsAlpha: Flow<Float> =
@@ -43,4 +45,7 @@
             onStep = { it },
             onCancel = { 0f },
         )
+
+    override val deviceEntryParentViewAlpha: Flow<Float> =
+        transitionAnimation.immediatelyTransitionTo(1f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
index 942903b..009f85d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
@@ -19,9 +19,12 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -42,11 +45,14 @@
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
     private val transitionAnimation =
-        animationFlow.setup(
-            duration = FromPrimaryBouncerTransitionInteractor.TO_AOD_DURATION,
-            from = KeyguardState.PRIMARY_BOUNCER,
-            to = KeyguardState.AOD,
-        )
+        animationFlow
+            .setup(
+                duration = FromPrimaryBouncerTransitionInteractor.TO_AOD_DURATION,
+                edge = Edge.create(from = Scenes.Bouncer, to = AOD),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = PRIMARY_BOUNCER, to = AOD),
+            )
 
     val deviceEntryBackgroundViewAlpha: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt
index 13f651a..e5bb464 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt
@@ -19,9 +19,12 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_DOZING_DURATION
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -42,11 +45,14 @@
 ) : DeviceEntryIconTransition {
 
     private val transitionAnimation =
-        animationFlow.setup(
-            duration = TO_DOZING_DURATION,
-            from = KeyguardState.PRIMARY_BOUNCER,
-            to = KeyguardState.DOZING,
-        )
+        animationFlow
+            .setup(
+                duration = TO_DOZING_DURATION,
+                edge = Edge.create(from = Scenes.Bouncer, to = DOZING),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = PRIMARY_BOUNCER, to = DOZING),
+            )
 
     val deviceEntryBackgroundViewAlpha: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index b1fa710..7ae4558 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -20,11 +20,13 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.shared.model.ScrimAlpha
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import dagger.Lazy
 import javax.inject.Inject
@@ -49,11 +51,14 @@
     animationFlow: KeyguardTransitionAnimationFlow,
 ) {
     private val transitionAnimation =
-        animationFlow.setup(
-            duration = TO_GONE_DURATION,
-            from = PRIMARY_BOUNCER,
-            to = GONE,
-        )
+        animationFlow
+            .setup(
+                duration = TO_GONE_DURATION,
+                edge = Edge.create(from = PRIMARY_BOUNCER, to = Scenes.Gone),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = PRIMARY_BOUNCER, to = GONE),
+            )
 
     private var leaveShadeOpen: Boolean = false
     private var willRunDismissFromKeyguard: Boolean = false
@@ -88,6 +93,7 @@
         } else {
             createBouncerAlphaFlow(primaryBouncerInteractor::willRunDismissFromKeyguard)
         }
+
     private fun createBouncerAlphaFlow(willRunAnimationOnKeyguard: () -> Boolean): Flow<Float> {
         return transitionAnimation.sharedFlow(
             duration = 200.milliseconds,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
index 2575041..7511101 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
@@ -19,9 +19,12 @@
 import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -39,11 +42,14 @@
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
     private val transitionAnimation =
-        animationFlow.setup(
-            duration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION,
-            from = KeyguardState.PRIMARY_BOUNCER,
-            to = KeyguardState.LOCKSCREEN,
-        )
+        animationFlow
+            .setup(
+                duration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION,
+                edge = Edge.create(from = Scenes.Bouncer, to = LOCKSCREEN),
+            )
+            .setupWithoutSceneContainer(
+                edge = Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN),
+            )
 
     val shortcutsAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index 9e6c552..b276f53 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -201,6 +201,10 @@
         )
     }
 
+    fun addLockoutResetCallbackDone() {
+        logBuffer.log(TAG, DEBUG, {}, { "addlockoutResetCallback done" })
+    }
+
     fun authRequested(uiEvent: FaceAuthUiEvent) {
         logBuffer.log(
             TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
index 9719c02..0c70f10 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
@@ -107,14 +107,11 @@
             .thenByDescending { it.updateTime }
             .thenByDescending { it.notificationKey }
 
-    private val _sortedMedia: MutableStateFlow<TreeMap<MediaSortKeyModel, MediaCommonModel>> =
-        MutableStateFlow(TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator))
-    val sortedMedia: StateFlow<Map<MediaSortKeyModel, MediaCommonModel>> =
-        _sortedMedia.asStateFlow()
+    private val _currentMedia: MutableStateFlow<List<MediaCommonModel>> =
+        MutableStateFlow(mutableListOf())
+    val currentMedia = _currentMedia.asStateFlow()
 
-    private val _isMediaFromRec: MutableStateFlow<Boolean> = MutableStateFlow(false)
-    val isMediaFromRec: StateFlow<Boolean> = _isMediaFromRec.asStateFlow()
-
+    private var sortedMedia = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator)
     private var mediaFromRecPackageName: String? = null
     private var locale: Locale = applicationContext.resources.configuration.locales.get(0)
 
@@ -186,7 +183,7 @@
     fun addMediaDataLoadingState(mediaDataLoadingModel: MediaDataLoadingModel) {
         val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator)
         sortedMap.putAll(
-            _sortedMedia.value.filter { (_, commonModel) ->
+            sortedMedia.filter { (_, commonModel) ->
                 commonModel !is MediaCommonModel.MediaControl ||
                     commonModel.mediaLoadedModel.instanceId != mediaDataLoadingModel.instanceId
             }
@@ -207,18 +204,52 @@
                 )
 
             if (mediaDataLoadingModel is MediaDataLoadingModel.Loaded) {
-                val isMediaFromRec = isMediaFromRec(it)
+                val newCommonModel =
+                    MediaCommonModel.MediaControl(
+                        mediaDataLoadingModel,
+                        canBeRemoved(it),
+                        isMediaFromRec(it)
+                    )
+                sortedMap[sortKey] = newCommonModel
 
-                _isMediaFromRec.value = isMediaFromRec
-                if (isMediaFromRec) {
-                    mediaFromRecPackageName = null
+                // On Addition or tapping on recommendations, we should show the new order of media.
+                if (mediaFromRecPackageName == it.packageName) {
+                    if (it.isPlaying == true) {
+                        mediaFromRecPackageName = null
+                        _currentMedia.value = sortedMap.values.toList()
+                    }
+                } else if (sortedMap.size > sortedMedia.size) {
+                    _currentMedia.value = sortedMap.values.toList()
+                } else if (sortedMap.size == sortedMedia.size) {
+                    // When loading an update for an existing media control.
+                    val currentList =
+                        mutableListOf<MediaCommonModel>().apply { addAll(_currentMedia.value) }
+                    currentList.forEachIndexed { index, mediaCommonModel ->
+                        if (
+                            mediaCommonModel is MediaCommonModel.MediaControl &&
+                                mediaCommonModel.mediaLoadedModel.instanceId ==
+                                    mediaDataLoadingModel.instanceId &&
+                                mediaCommonModel != newCommonModel
+                        ) {
+                            // Update media model if changed.
+                            currentList[index] = newCommonModel
+                        }
+                    }
+                    _currentMedia.value = currentList
                 }
-                sortedMap[sortKey] =
-                    MediaCommonModel.MediaControl(mediaDataLoadingModel, canBeRemoved(it))
             }
         }
 
-        _sortedMedia.value = sortedMap
+        sortedMedia = sortedMap
+
+        // On removal we want to keep the order being shown to user.
+        if (mediaDataLoadingModel is MediaDataLoadingModel.Removed) {
+            _currentMedia.value =
+                _currentMedia.value.filter { commonModel ->
+                    commonModel !is MediaCommonModel.MediaControl ||
+                        mediaDataLoadingModel.instanceId != commonModel.mediaLoadedModel.instanceId
+                }
+        }
     }
 
     fun setRecommendationsLoadingState(smartspaceMediaLoadingModel: SmartspaceMediaLoadingModel) {
@@ -229,7 +260,7 @@
             }
         val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator)
         sortedMap.putAll(
-            _sortedMedia.value.filter { (_, commonModel) ->
+            sortedMedia.filter { (_, commonModel) ->
                 commonModel !is MediaCommonModel.MediaRecommendations
             }
         )
@@ -240,11 +271,25 @@
                 isPlaying = false,
                 active = _smartspaceMediaData.value.isActive,
             )
-        if (smartspaceMediaLoadingModel is SmartspaceMediaLoadingModel.Loaded) {
-            sortedMap[sortKey] = MediaCommonModel.MediaRecommendations(smartspaceMediaLoadingModel)
+        when (smartspaceMediaLoadingModel) {
+            is SmartspaceMediaLoadingModel.Loaded ->
+                sortedMap[sortKey] =
+                    MediaCommonModel.MediaRecommendations(smartspaceMediaLoadingModel)
+            is SmartspaceMediaLoadingModel.Removed ->
+                _currentMedia.value =
+                    _currentMedia.value.filter { commonModel ->
+                        commonModel !is MediaCommonModel.MediaRecommendations
+                    }
         }
 
-        _sortedMedia.value = sortedMap
+        if (sortedMap.size > sortedMedia.size) {
+            _currentMedia.value = sortedMap.values.toList()
+        }
+        sortedMedia = sortedMap
+    }
+
+    fun setOrderedMedia() {
+        _currentMedia.value = sortedMedia.values.toList()
     }
 
     fun setMediaFromRecPackageName(packageName: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt
index c02478b..96ef7d2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt
@@ -206,11 +206,11 @@
         listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
     }
 
-    override fun onMediaDataRemoved(key: String) {
+    override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
         allEntries.remove(key)
         userEntries.remove(key)?.let {
             // Only notify listeners if something actually changed
-            listeners.forEach { it.onMediaDataRemoved(key) }
+            listeners.forEach { it.onMediaDataRemoved(key, userInitiated) }
         }
     }
 
@@ -246,7 +246,7 @@
                 // Only remove media when the profile is unavailable.
                 if (DEBUG) Log.d(TAG, "Removing $key after profile change")
                 userEntries.remove(key, data)
-                listeners.forEach { listener -> listener.onMediaDataRemoved(key) }
+                listeners.forEach { listener -> listener.onMediaDataRemoved(key, false) }
             }
         }
     }
@@ -261,7 +261,7 @@
         userEntries.clear()
         keyCopy.forEach {
             if (DEBUG) Log.d(TAG, "Removing $it after user change")
-            listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) }
+            listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it, false) }
         }
 
         allEntries.forEach { (key, data) ->
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
index 3a831156..143d66b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
@@ -545,8 +545,8 @@
      * External listeners registered with [addListener] will be notified after the event propagates
      * through the internal listener pipeline.
      */
-    private fun notifyMediaDataRemoved(key: String) {
-        internalListeners.forEach { it.onMediaDataRemoved(key) }
+    private fun notifyMediaDataRemoved(key: String, userInitiated: Boolean = false) {
+        internalListeners.forEach { it.onMediaDataRemoved(key, userInitiated) }
     }
 
     /**
@@ -578,7 +578,7 @@
             if (it.active == !timedOut && !forceUpdate) {
                 if (it.resumption) {
                     if (DEBUG) Log.d(TAG, "timing out resume player $key")
-                    dismissMediaData(key, 0L /* delay */)
+                    dismissMediaData(key, delay = 0L, userInitiated = false)
                 }
                 return
             }
@@ -627,17 +627,17 @@
         }
     }
 
-    private fun removeEntry(key: String, logEvent: Boolean = true) {
+    private fun removeEntry(key: String, logEvent: Boolean = true, userInitiated: Boolean = false) {
         mediaEntries.remove(key)?.let {
             if (logEvent) {
                 logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
             }
         }
-        notifyMediaDataRemoved(key)
+        notifyMediaDataRemoved(key, userInitiated)
     }
 
     /** Dismiss a media entry. Returns false if the key was not found. */
-    override fun dismissMediaData(key: String, delay: Long): Boolean {
+    override fun dismissMediaData(key: String, delay: Long, userInitiated: Boolean): Boolean {
         val existed = mediaEntries[key] != null
         backgroundExecutor.execute {
             mediaEntries[key]?.let { mediaData ->
@@ -649,7 +649,10 @@
                 }
             }
         }
-        foregroundExecutor.executeDelayed({ removeEntry(key) }, delay)
+        foregroundExecutor.executeDelayed(
+            { removeEntry(key = key, userInitiated = userInitiated) },
+            delay
+        )
         return existed
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt
index ad70db5..88910f9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt
@@ -53,8 +53,8 @@
         listeners.toSet().forEach { it.onSmartspaceMediaDataLoaded(key, data) }
     }
 
-    override fun onMediaDataRemoved(key: String) {
-        remove(key)
+    override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
+        remove(key, userInitiated)
     }
 
     override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
@@ -71,8 +71,8 @@
         }
     }
 
-    override fun onKeyRemoved(key: String) {
-        remove(key)
+    override fun onKeyRemoved(key: String, userInitiated: Boolean) {
+        remove(key, userInitiated)
     }
 
     /**
@@ -92,10 +92,10 @@
         }
     }
 
-    private fun remove(key: String) {
+    private fun remove(key: String, userInitiated: Boolean) {
         entries.remove(key)?.let {
             val listenersCopy = listeners.toSet()
-            listenersCopy.forEach { it.onMediaDataRemoved(key) }
+            listenersCopy.forEach { it.onMediaDataRemoved(key, userInitiated) }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
index 5432a18..8d19ce8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
@@ -213,7 +213,7 @@
         listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
     }
 
-    override fun onMediaDataRemoved(key: String) {
+    override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
         mediaFilterRepository.removeMediaEntry(key)?.let { mediaData ->
             val instanceId = mediaData.instanceId
             mediaFilterRepository.removeSelectedUserMediaEntry(instanceId)?.let {
@@ -221,7 +221,7 @@
                     MediaDataLoadingModel.Removed(instanceId)
                 )
                 // Only notify listeners if something actually changed
-                listeners.forEach { it.onMediaDataRemoved(key) }
+                listeners.forEach { it.onMediaDataRemoved(key, userInitiated) }
             }
         }
     }
@@ -270,7 +270,7 @@
                 mediaFilterRepository.addMediaDataLoadingState(
                     MediaDataLoadingModel.Removed(data.instanceId)
                 )
-                listeners.forEach { listener -> listener.onMediaDataRemoved(key) }
+                listeners.forEach { listener -> listener.onMediaDataRemoved(key, false) }
             }
         }
     }
@@ -288,7 +288,7 @@
                 MediaDataLoadingModel.Removed(instanceId)
             )
             getKey(instanceId)?.let {
-                listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) }
+                listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it, false) }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
index 2331aa21..8099e59 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
@@ -60,7 +60,7 @@
     )
 
     /** Dismiss a media entry. Returns false if the key was not found. */
-    fun dismissMediaData(key: String, delay: Long): Boolean
+    fun dismissMediaData(key: String, delay: Long, userInitiated: Boolean): Boolean
 
     /**
      * Called whenever the recommendation has been expired or removed by the user. This will remove
@@ -136,7 +136,7 @@
         ) {}
 
         /** Called whenever a previously existing Media notification was removed. */
-        override fun onMediaDataRemoved(key: String) {}
+        override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {}
 
         /**
          * Called whenever a previously existing Smartspace media data was removed.
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
index 1d7c025..eed7752 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
@@ -498,8 +498,8 @@
      * External listeners registered with [MediaCarouselInteractor.addListener] will be notified
      * after the event propagates through the internal listener pipeline.
      */
-    private fun notifyMediaDataRemoved(key: String) {
-        internalListeners.forEach { it.onMediaDataRemoved(key) }
+    private fun notifyMediaDataRemoved(key: String, userInitiated: Boolean = false) {
+        internalListeners.forEach { it.onMediaDataRemoved(key, userInitiated) }
     }
 
     /**
@@ -531,7 +531,7 @@
             if (it.active == !timedOut && !forceUpdate) {
                 if (it.resumption) {
                     if (DEBUG) Log.d(TAG, "timing out resume player $key")
-                    dismissMediaData(key, 0L /* delay */)
+                    dismissMediaData(key, delayMs = 0L, userInitiated = false)
                 }
                 return
             }
@@ -580,17 +580,17 @@
         }
     }
 
-    private fun removeEntry(key: String, logEvent: Boolean = true) {
+    private fun removeEntry(key: String, logEvent: Boolean = true, userInitiated: Boolean = false) {
         mediaDataRepository.removeMediaEntry(key)?.let {
             if (logEvent) {
                 logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
             }
         }
-        notifyMediaDataRemoved(key)
+        notifyMediaDataRemoved(key, userInitiated)
     }
 
     /** Dismiss a media entry. Returns false if the key was not found. */
-    fun dismissMediaData(key: String, delayMs: Long): Boolean {
+    fun dismissMediaData(key: String, delayMs: Long, userInitiated: Boolean): Boolean {
         val existed = mediaDataRepository.mediaEntries.value[key] != null
         backgroundExecutor.execute {
             mediaDataRepository.mediaEntries.value[key]?.let { mediaData ->
@@ -602,16 +602,19 @@
                 }
             }
         }
-        foregroundExecutor.executeDelayed({ removeEntry(key) }, delayMs)
+        foregroundExecutor.executeDelayed(
+            { removeEntry(key, userInitiated = userInitiated) },
+            delayMs
+        )
         return existed
     }
 
     /** Dismiss a media entry. Returns false if the corresponding key was not found. */
-    fun dismissMediaData(instanceId: InstanceId, delayMs: Long): Boolean {
+    fun dismissMediaData(instanceId: InstanceId, delayMs: Long, userInitiated: Boolean): Boolean {
         val mediaEntries = mediaDataRepository.mediaEntries.value
         val filteredEntries = mediaEntries.filter { (_, data) -> data.instanceId == instanceId }
         return if (filteredEntries.isNotEmpty()) {
-            dismissMediaData(filteredEntries.keys.first(), delayMs)
+            dismissMediaData(filteredEntries.keys.first(), delayMs, userInitiated)
         } else {
             false
         }
@@ -1579,7 +1582,7 @@
         ) {}
 
         /** Called whenever a previously existing Media notification was removed. */
-        fun onMediaDataRemoved(key: String) {}
+        fun onMediaDataRemoved(key: String, userInitiated: Boolean) {}
 
         /**
          * Called whenever a previously existing Smartspace media data was removed.
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
index 0e2814b..043fbfa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
@@ -111,10 +111,10 @@
         }
     }
 
-    override fun onMediaDataRemoved(key: String) {
+    override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
         val token = entries.remove(key)
         token?.stop()
-        token?.let { listeners.forEach { it.onKeyRemoved(key) } }
+        token?.let { listeners.forEach { it.onKeyRemoved(key, userInitiated) } }
     }
 
     fun dump(pw: PrintWriter) {
@@ -136,7 +136,7 @@
         /** Called when the route has changed for a given notification. */
         fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?)
         /** Called when the notification was removed. */
-        fun onKeyRemoved(key: String)
+        fun onKeyRemoved(key: String, userInitiated: Boolean)
     }
 
     private inner class Entry(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt
index b2a8f2e..b178d84 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt
@@ -137,7 +137,7 @@
                 // farther and dismiss the media data so that media controls for the local session
                 // don't hang around while casting.
                 if (!keyedTokens.get(key)!!.contains(TokenId(remote.sessionToken))) {
-                    dispatchMediaDataRemoved(key)
+                    dispatchMediaDataRemoved(key, userInitiated = false)
                 }
             }
         }
@@ -151,11 +151,11 @@
         backgroundExecutor.execute { dispatchSmartspaceMediaDataLoaded(key, data) }
     }
 
-    override fun onMediaDataRemoved(key: String) {
+    override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
         // Queue on background thread to ensure ordering of loaded and removed events is maintained.
         backgroundExecutor.execute {
             keyedTokens.remove(key)
-            dispatchMediaDataRemoved(key)
+            dispatchMediaDataRemoved(key, userInitiated)
         }
     }
 
@@ -174,8 +174,10 @@
         }
     }
 
-    private fun dispatchMediaDataRemoved(key: String) {
-        foregroundExecutor.execute { listeners.toSet().forEach { it.onMediaDataRemoved(key) } }
+    private fun dispatchMediaDataRemoved(key: String, userInitiated: Boolean) {
+        foregroundExecutor.execute {
+            listeners.toSet().forEach { it.onMediaDataRemoved(key, userInitiated) }
+        }
     }
 
     private fun dispatchSmartspaceMediaDataLoaded(key: String, info: SmartspaceMediaData) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
index 29f3967..fc31903 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
@@ -169,7 +169,7 @@
         mediaListeners[key] = PlaybackStateListener(key, data)
     }
 
-    override fun onMediaDataRemoved(key: String) {
+    override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
         mediaListeners.remove(key)?.destroy()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
index 33c0b19..9e62300 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
@@ -63,7 +63,7 @@
     private val mediaDeviceManager: MediaDeviceManager,
     private val mediaDataCombineLatest: MediaDataCombineLatest,
     private val mediaDataFilter: MediaDataFilterImpl,
-    mediaFilterRepository: MediaFilterRepository,
+    private val mediaFilterRepository: MediaFilterRepository,
     private val mediaFlags: MediaFlags,
 ) : MediaDataManager, CoreStartable {
 
@@ -123,18 +123,8 @@
                 initialValue = false,
             )
 
-    /** The most recent sorted set for user media instances */
-    val sortedMedia: StateFlow<List<MediaCommonModel>> =
-        mediaFilterRepository.sortedMedia
-            .mapLatest { it.values.toList() }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = emptyList(),
-            )
-
-    /** Whether the current change in media was done by clicking on a recommendation */
-    val isMediaFromRec: StateFlow<Boolean> = mediaFilterRepository.isMediaFromRec
+    /** The current list for user media instances */
+    val currentMedia: StateFlow<List<MediaCommonModel>> = mediaFilterRepository.currentMedia
 
     override fun start() {
         if (!mediaFlags.isMediaControlsRefactorEnabled()) {
@@ -215,12 +205,12 @@
         )
     }
 
-    override fun dismissMediaData(key: String, delay: Long): Boolean {
-        return mediaDataProcessor.dismissMediaData(key, delay)
+    override fun dismissMediaData(key: String, delay: Long, userInitiated: Boolean): Boolean {
+        return mediaDataProcessor.dismissMediaData(key, delay, userInitiated)
     }
 
     fun removeMediaControl(instanceId: InstanceId, delay: Long) {
-        mediaDataProcessor.dismissMediaData(instanceId, delay)
+        mediaDataProcessor.dismissMediaData(instanceId, delay, userInitiated = false)
     }
 
     override fun dismissSmartspaceRecommendation(key: String, delay: Long) {
@@ -251,6 +241,10 @@
 
     override fun isRecommendationActive() = mediaDataRepository.smartspaceMediaData.value.isActive
 
+    fun reorderMedia() {
+        mediaFilterRepository.setOrderedMedia()
+    }
+
     /** Add a listener for internal events. */
     private fun addInternalListener(listener: MediaDataManager.Listener) =
         mediaDataProcessor.addInternalListener(listener)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
index 9f2d132..1a0f582 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
@@ -21,9 +21,7 @@
 import android.app.PendingIntent
 import android.content.Context
 import android.content.Intent
-import android.media.session.MediaController
 import android.media.session.MediaSession
-import android.media.session.PlaybackState
 import android.provider.Settings
 import android.util.Log
 import com.android.internal.jank.Cuj
@@ -42,7 +40,6 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.kotlin.pairwiseBy
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.flow.Flow
@@ -70,19 +67,6 @@
             .map { entries -> entries[instanceId]?.let { toMediaControlModel(it) } }
             .distinctUntilChanged()
 
-    val isStartedPlaying: Flow<Boolean> =
-        mediaControl
-            .map { mediaControl ->
-                mediaControl?.token?.let { token ->
-                    MediaController(applicationContext, token).playbackState?.let {
-                        it.state == PlaybackState.STATE_PLAYING
-                    }
-                }
-                    ?: false
-            }
-            .pairwiseBy(initialValue = false) { wasPlaying, isPlaying -> !wasPlaying && isPlaying }
-            .distinctUntilChanged()
-
     val onAnyMediaConfigurationChange: Flow<Unit> = repository.onAnyMediaConfigurationChange
 
     fun removeMediaControl(
@@ -90,7 +74,8 @@
         instanceId: InstanceId,
         delayMs: Long
     ): Boolean {
-        val dismissed = mediaDataProcessor.dismissMediaData(instanceId, delayMs)
+        val dismissed =
+            mediaDataProcessor.dismissMediaData(instanceId, delayMs, userInitiated = true)
         if (!dismissed) {
             Log.w(
                 TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt
index 23860bb..56cc618 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt
@@ -21,6 +21,7 @@
     data class MediaControl(
         val mediaLoadedModel: MediaDataLoadingModel.Loaded,
         val canBeRemoved: Boolean = false,
+        val isMediaFromRec: Boolean = false,
     ) : MediaCommonModel()
 
     data class MediaRecommendations(val recsLoadingModel: SmartspaceMediaLoadingModel) :
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
index 73fb558..fed93f0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
@@ -260,44 +260,50 @@
             }
 
             SEMANTIC_ACTIONS_ALL.forEachIndexed { index, id ->
-                val button = viewHolder.getAction(id)
-                val actionViewModel = viewModel.actionButtons[index]
-                if (button.id == R.id.actionPrev) {
-                    actionViewModel?.let {
-                        viewController.setUpPrevButtonInfo(true, it.notVisibleValue)
-                    }
-                } else if (button.id == R.id.actionNext) {
-                    actionViewModel?.let {
-                        viewController.setUpNextButtonInfo(true, it.notVisibleValue)
-                    }
+                val buttonView = viewHolder.getAction(id)
+                val buttonModel = viewModel.actionButtons[index]
+                if (buttonView.id == R.id.actionPrev) {
+                    viewController.setUpPrevButtonInfo(
+                        buttonModel.isEnabled,
+                        buttonModel.notVisibleValue
+                    )
+                } else if (buttonView.id == R.id.actionNext) {
+                    viewController.setUpNextButtonInfo(
+                        buttonModel.isEnabled,
+                        buttonModel.notVisibleValue
+                    )
                 }
-                actionViewModel?.let { action ->
-                    val animHandler = (button.tag ?: AnimationBindHandler()) as AnimationBindHandler
-                    animHandler.tryExecute {
-                        if (animHandler.updateRebindId(action.rebindId)) {
+                val animHandler = (buttonView.tag ?: AnimationBindHandler()) as AnimationBindHandler
+                animHandler.tryExecute {
+                    if (buttonModel.isEnabled) {
+                        if (animHandler.updateRebindId(buttonModel.rebindId)) {
                             animHandler.unregisterAll()
-                            animHandler.tryRegister(action.icon)
-                            animHandler.tryRegister(action.background)
+                            animHandler.tryRegister(buttonModel.icon)
+                            animHandler.tryRegister(buttonModel.background)
                             bindButtonCommon(
-                                button,
+                                buttonView,
                                 viewHolder.multiRippleView,
-                                action,
+                                buttonModel,
                                 viewController,
                                 falsingManager,
                             )
                         }
-                        val visible = action.isVisibleWhenScrubbing || !viewController.isScrubbing
-                        setSemanticButtonVisibleAndAlpha(
-                            viewHolder.getAction(id),
-                            viewController.expandedLayout,
-                            viewController.collapsedLayout,
-                            visible,
-                            action.notVisibleValue,
-                            action.showInCollapsed
-                        )
+                    } else {
+                        animHandler.unregisterAll()
+                        clearButton(buttonView)
                     }
+                    val visible =
+                        buttonModel.isEnabled &&
+                            (buttonModel.isVisibleWhenScrubbing || !viewController.isScrubbing)
+                    setSemanticButtonVisibleAndAlpha(
+                        viewHolder.getAction(id),
+                        viewController.expandedLayout,
+                        viewController.collapsedLayout,
+                        visible,
+                        buttonModel.notVisibleValue,
+                        buttonModel.showInCollapsed
+                    )
                 }
-                    ?: clearButton(button)
             }
         } else {
             // Hide buttons that only appear for semantic actions
@@ -309,22 +315,16 @@
             // Set all generic buttons
             genericButtons.forEachIndexed { index, button ->
                 if (index < viewModel.actionButtons.size) {
-                    viewModel.actionButtons[index]?.let { action ->
-                        bindButtonCommon(
-                            button,
-                            viewHolder.multiRippleView,
-                            action,
-                            viewController,
-                            falsingManager,
-                        )
-                        setVisibleAndAlpha(expandedSet, button.id, visible = true)
-                        setVisibleAndAlpha(
-                            collapsedSet,
-                            button.id,
-                            visible = action.showInCollapsed
-                        )
-                    }
-                        ?: clearButton(button)
+                    val action = viewModel.actionButtons[index]
+                    bindButtonCommon(
+                        button,
+                        viewHolder.multiRippleView,
+                        action,
+                        viewController,
+                        falsingManager,
+                    )
+                    setVisibleAndAlpha(expandedSet, button.id, visible = true)
+                    setVisibleAndAlpha(collapsedSet, button.id, visible = action.showInCollapsed)
                 } else {
                     // Hide any unused buttons
                     clearButton(button)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 0478178..19e3e07 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -46,6 +46,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
@@ -73,6 +74,9 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.PageIndicator
 import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shared.system.SysUiStatsLog
 import com.android.systemui.shared.system.SysUiStatsLog.SMARTSPACE_CARD_REPORTED
 import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD
@@ -142,6 +146,7 @@
     private val secureSettings: SecureSettings,
     private val mediaCarouselViewModel: MediaCarouselViewModel,
     private val mediaViewControllerFactory: Provider<MediaViewController>,
+    private val sceneInteractor: SceneInteractor,
 ) : Dumpable {
     /** The current width of the carousel */
     var currentCarouselWidth: Int = 0
@@ -190,9 +195,11 @@
     @VisibleForTesting
     lateinit var settingsButton: View
         private set
+
     private val mediaContent: ViewGroup
     @VisibleForTesting var pageIndicator: PageIndicator
     private var needsReordering: Boolean = false
+    private var isUserInitiatedRemovalQueued: Boolean = false
     private var keysNeedRemoval = mutableSetOf<String>()
     var shouldScrollToKey: Boolean = false
     private var isRtl: Boolean = false
@@ -301,7 +308,11 @@
      * It will be called when the container is out of view.
      */
     lateinit var updateUserVisibility: () -> Unit
-    lateinit var updateHostVisibility: () -> Unit
+    var updateHostVisibility: () -> Unit = {}
+        set(value) {
+            field = value
+            mediaCarouselViewModel.updateHostVisibility = value
+        }
 
     private val isReorderingAllowed: Boolean
         get() = visualStabilityProvider.isReorderingAllowed
@@ -338,6 +349,20 @@
         configurationController.addCallback(configListener)
         if (!mediaFlags.isMediaControlsRefactorEnabled()) {
             setUpListeners()
+        } else {
+            val visualStabilityCallback = OnReorderingAllowedListener {
+                mediaCarouselViewModel.onReorderingAllowed()
+
+                // Update user visibility so that no extra impression will be logged when
+                // activeMediaIndex resets to 0
+                if (this::updateUserVisibility.isInitialized) {
+                    updateUserVisibility()
+                }
+
+                // Let's reset our scroll position
+                mediaCarouselScrollHandler.scrollToStart()
+            }
+            visualStabilityProvider.addPersistentReorderingAllowedListener(visualStabilityCallback)
         }
         mediaFrame.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
             // The pageIndicator is not laid out yet when we get the current state update,
@@ -383,12 +408,15 @@
                 reorderAllPlayers(previousVisiblePlayerKey = null)
             }
 
-            keysNeedRemoval.forEach { removePlayer(it) }
+            keysNeedRemoval.forEach {
+                removePlayer(it, userInitiated = isUserInitiatedRemovalQueued)
+            }
             if (keysNeedRemoval.size > 0) {
                 // Carousel visibility may need to be updated after late removals
                 updateHostVisibility()
             }
             keysNeedRemoval.clear()
+            isUserInitiatedRemovalQueued = false
 
             // Update user visibility so that no extra impression will be logged when
             // activeMediaIndex resets to 0
@@ -472,18 +500,18 @@
 
                     val canRemove = data.isPlaying?.let { !it } ?: data.isClearable && !data.active
                     if (canRemove && !Utils.useMediaResumption(context)) {
-                        // This view isn't playing, let's remove this! This happens e.g. when
-                        // dismissing/timing out a view. We still have the data around because
-                        // resumption could be on, but we should save the resources and release
-                        // this.
+                        // This media control is both paused and timed out, and the resumption
+                        // setting is off - let's remove it
                         if (isReorderingAllowed) {
-                            onMediaDataRemoved(key)
+                            onMediaDataRemoved(key, userInitiated = MediaPlayerData.isSwipedAway)
                         } else {
+                            isUserInitiatedRemovalQueued = MediaPlayerData.isSwipedAway
                             keysNeedRemoval.add(key)
                         }
                     } else {
                         keysNeedRemoval.remove(key)
                     }
+                    MediaPlayerData.isSwipedAway = false
                 }
 
                 override fun onSmartspaceMediaDataLoaded(
@@ -563,11 +591,12 @@
                             addSmartspaceMediaRecommendations(key, data, shouldPrioritize)
                         }
                     }
+                    MediaPlayerData.isSwipedAway = false
                 }
 
-                override fun onMediaDataRemoved(key: String) {
-                    debugLogger.logMediaRemoved(key)
-                    removePlayer(key)
+                override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
+                    debugLogger.logMediaRemoved(key, userInitiated)
+                    removePlayer(key, userInitiated = userInitiated)
                 }
 
                 override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
@@ -577,9 +606,7 @@
                         if (!immediately) {
                             // Although it wasn't requested, we were able to process the removal
                             // immediately since reordering is allowed. So, notify hosts to update
-                            if (this@MediaCarouselController::updateHostVisibility.isInitialized) {
-                                updateHostVisibility()
-                            }
+                            updateHostVisibility()
                         }
                     } else {
                         keysNeedRemoval.add(key)
@@ -630,9 +657,13 @@
     @VisibleForTesting
     internal fun listenForAnyStateToGoneKeyguardTransition(scope: CoroutineScope): Job {
         return scope.launch {
-            keyguardTransitionInteractor
-                .transition(to = GONE)
-                .filter { it.transitionState == TransitionState.FINISHED }
+            if (SceneContainerFlag.isEnabled) {
+                    sceneInteractor.transitionState.filter { it.isIdle(Scenes.Gone) }
+                } else {
+                    keyguardTransitionInteractor.transition(Edge.create(to = GONE)).filter {
+                        it.transitionState == TransitionState.FINISHED
+                    }
+                }
                 .collect {
                     showMediaCarousel()
                     updateHostVisibility()
@@ -644,7 +675,7 @@
     internal fun listenForAnyStateToLockscreenTransition(scope: CoroutineScope): Job {
         return scope.launch {
             keyguardTransitionInteractor
-                .transition(to = LOCKSCREEN)
+                .transition(Edge.create(to = LOCKSCREEN))
                 .filter { it.transitionState == TransitionState.FINISHED }
                 .collect {
                     if (!allowMediaPlayerOnLockScreen) {
@@ -732,12 +763,20 @@
             }
         }
         viewController.setListening(mediaCarouselScrollHandler.visibleToUser && currentlyExpanded)
+        controllerByViewModel[commonViewModel] = viewController
         updateViewControllerToState(viewController, noAnimation = true)
         updatePageIndicator()
+        if (
+            commonViewModel is MediaCommonViewModel.MediaControl && commonViewModel.isMediaFromRec
+        ) {
+            mediaCarouselScrollHandler.scrollToPlayer(
+                mediaCarouselScrollHandler.visibleMediaIndex,
+                destIndex = 0
+            )
+        }
         mediaCarouselScrollHandler.onPlayersChanged()
         mediaFrame.requiresRemeasuring = true
         commonViewModel.onAdded(commonViewModel)
-        controllerByViewModel[commonViewModel] = viewController
     }
 
     private fun onUpdated(commonViewModel: MediaCommonViewModel) {
@@ -1023,7 +1062,8 @@
     fun removePlayer(
         key: String,
         dismissMediaData: Boolean = true,
-        dismissRecommendation: Boolean = true
+        dismissRecommendation: Boolean = true,
+        userInitiated: Boolean = false,
     ): MediaControlPanel? {
         if (key == MediaPlayerData.smartspaceMediaKey()) {
             MediaPlayerData.smartspaceMediaData?.let {
@@ -1042,7 +1082,7 @@
 
             if (dismissMediaData) {
                 // Inform the media manager of a potentially late dismissal
-                mediaManager.dismissMediaData(key, delay = 0L)
+                mediaManager.dismissMediaData(key, delay = 0L, userInitiated = userInitiated)
             }
             if (dismissRecommendation) {
                 // Inform the media manager of a potentially late dismissal
@@ -1502,7 +1542,8 @@
         }
     }
 
-    private fun onSwipeToDismiss() {
+    @VisibleForTesting
+    fun onSwipeToDismiss() {
         if (mediaFlags.isMediaControlsRefactorEnabled()) {
             mediaCarouselViewModel.onSwipeToDismiss()
             return
@@ -1521,6 +1562,7 @@
                 it.mIsImpressed = false
             }
         }
+        MediaPlayerData.isSwipedAway = true
         logger.logSwipeDismiss()
         mediaManager.onSwipeToDismiss()
     }
@@ -1547,6 +1589,7 @@
                 "state: ${desiredHostState?.expansion}, " +
                     "only active ${desiredHostState?.showsOnlyActiveMedia}"
             )
+            println("isSwipedAway: ${MediaPlayerData.isSwipedAway}")
         }
     }
 }
@@ -1577,6 +1620,7 @@
     // Whether should prioritize Smartspace card.
     internal var shouldPrioritizeSs: Boolean = false
         private set
+
     internal var smartspaceMediaData: SmartspaceMediaData? = null
         private set
 
@@ -1585,7 +1629,7 @@
         val data: MediaData,
         val key: String,
         val updateTime: Long = 0,
-        val isSsReactivated: Boolean = false
+        val isSsReactivated: Boolean = false,
     )
 
     private val comparator =
@@ -1610,6 +1654,9 @@
     // A map that tracks order of visible media players before they get reordered.
     private val visibleMediaPlayers = LinkedHashMap<String, MediaSortKey>()
 
+    // Whether the user swiped away the carousel since its last update
+    internal var isSwipedAway: Boolean = false
+
     fun addMediaPlayer(
         key: String,
         data: MediaData,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
index ebf1c6a..1be25a7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
@@ -53,8 +53,16 @@
             { "add player $str1, active: $bool1" }
         )
 
-    fun logMediaRemoved(key: String) =
-        buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "removing player $str1" })
+    fun logMediaRemoved(key: String, userInitiated: Boolean) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = key
+                bool1 = userInitiated
+            },
+            { "removing player $str1, by user $bool1" }
+        )
 
     fun logRecommendationLoaded(key: String, isActive: Boolean) =
         buffer.log(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index e6c785e..0bc3c439 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -786,10 +786,11 @@
             if (mKey != null) {
                 closeGuts();
                 if (!mMediaDataManagerLazy.get().dismissMediaData(mKey,
-                        MediaViewController.GUTS_ANIMATION_DURATION + 100)) {
+                        /* delay */ MediaViewController.GUTS_ANIMATION_DURATION + 100,
+                        /* userInitiated */ true)) {
                     Log.w(TAG, "Manager failed to dismiss media " + mKey);
                     // Remove directly from carousel so user isn't stuck with defunct controls
-                    mMediaCarouselController.removePlayer(mKey, false, false);
+                    mMediaCarouselController.removePlayer(mKey, false, false, true);
                 }
             } else {
                 Log.w(TAG, "Dismiss media with null notification. Token uid="
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index 2b59858..3837708 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -709,12 +709,6 @@
 
         // For Turbulence noise.
         val loadingEffectView = mediaViewHolder.loadingEffectView
-        turbulenceNoiseAnimationConfig =
-            createTurbulenceNoiseConfig(
-                loadingEffectView,
-                turbulenceNoiseView,
-                colorSchemeTransition
-            )
         noiseDrawCallback =
             object : PaintDrawCallback {
                 override fun onDraw(paint: Paint) {
@@ -809,6 +803,14 @@
 
     fun setUpTurbulenceNoise() {
         if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+        if (!this::turbulenceNoiseAnimationConfig.isInitialized) {
+            turbulenceNoiseAnimationConfig =
+                createTurbulenceNoiseConfig(
+                    mediaViewHolder.loadingEffectView,
+                    mediaViewHolder.turbulenceNoiseView,
+                    colorSchemeTransition
+                )
+        }
         if (Flags.shaderlibLoadingEffectRefactor()) {
             if (!this::loadingEffect.isInitialized) {
                 loadingEffect =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
index eca76b6..91050c8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
@@ -105,7 +105,7 @@
                 updateViewVisibility()
             }
 
-            override fun onMediaDataRemoved(key: String) {
+            override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
                 updateViewVisibility()
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
index 96a8239..4e90936 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
@@ -26,21 +26,15 @@
 import com.android.systemui.media.controls.shared.model.MediaCommonModel
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
-import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
 import com.android.systemui.util.Utils
-import com.android.systemui.util.kotlin.pairwiseBy
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import java.util.concurrent.Executor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
 /** Models UI state and handles user inputs for media carousel */
@@ -60,46 +54,32 @@
     private val mediaFlags: MediaFlags,
 ) {
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     val mediaItems: StateFlow<List<MediaCommonViewModel>> =
-        conflatedCallbackFlow {
-                val listener = OnReorderingAllowedListener { trySend(Unit) }
-                visualStabilityProvider.addPersistentReorderingAllowedListener(listener)
-                trySend(Unit)
-                awaitClose { visualStabilityProvider.removeReorderingAllowedListener(listener) }
-            }
-            .flatMapLatest {
-                combine(interactor.isMediaFromRec, interactor.sortedMedia) {
-                    isRecsToMedia,
-                    sortedItems ->
-                    buildList {
-                        shouldReorder = isRecsToMedia
-                        val reorderAllowed = isReorderingAllowed()
-                        sortedItems.forEach { commonModel ->
-                            if (!reorderAllowed || !modelsPendingRemoval.contains(commonModel)) {
-                                when (commonModel) {
-                                    is MediaCommonModel.MediaControl ->
-                                        add(toViewModel(commonModel))
-                                    is MediaCommonModel.MediaRecommendations ->
-                                        add(toViewModel(commonModel))
-                                }
+        interactor.currentMedia
+            .map { sortedItems ->
+                val mediaList = buildList {
+                    sortedItems.forEach { commonModel ->
+                        // When view is started we should make sure to clean models that are pending
+                        // removal.
+                        // This action should only be triggered once.
+                        if (!allowReorder || !modelsPendingRemoval.contains(commonModel)) {
+                            when (commonModel) {
+                                is MediaCommonModel.MediaControl -> add(toViewModel(commonModel))
+                                is MediaCommonModel.MediaRecommendations ->
+                                    add(toViewModel(commonModel))
                             }
                         }
-                        if (reorderAllowed) {
-                            modelsPendingRemoval.clear()
-                        }
                     }
                 }
-            }
-            .pairwiseBy { old, new ->
-                // This condition can only happen when view is attached. So the old emit is of the
-                // most recent list updated.
-                // If the old list is empty, it is okay to emit the new ordered list.
-                if (isReorderingAllowed() || shouldReorder || old.isEmpty()) {
-                    new
-                } else {
-                    old
+                if (allowReorder) {
+                    if (modelsPendingRemoval.size > 0) {
+                        updateHostVisibility()
+                    }
+                    modelsPendingRemoval.clear()
                 }
+                allowReorder = false
+
+                mediaList
             }
             .stateIn(
                 scope = applicationScope,
@@ -107,6 +87,8 @@
                 initialValue = emptyList(),
             )
 
+    var updateHostVisibility: () -> Unit = {}
+
     private val mediaControlByInstanceId =
         mutableMapOf<InstanceId, MediaCommonViewModel.MediaControl>()
 
@@ -114,13 +96,18 @@
 
     private var modelsPendingRemoval: MutableSet<MediaCommonModel> = mutableSetOf()
 
-    private var shouldReorder = true
+    private var allowReorder = false
 
     fun onSwipeToDismiss() {
         logger.logSwipeDismiss()
         interactor.onSwipeToDismiss()
     }
 
+    fun onReorderingAllowed() {
+        allowReorder = true
+        interactor.reorderMedia()
+    }
+
     private fun toViewModel(
         commonModel: MediaCommonModel.MediaControl
     ): MediaCommonViewModel.MediaControl {
@@ -138,6 +125,7 @@
                         mediaControlByInstanceId.remove(instanceId)
                     },
                     onUpdated = { onMediaControlAddedOrUpdated(it, commonModel) },
+                    isMediaFromRec = commonModel.isMediaFromRec
                 )
                 .also { mediaControlByInstanceId[instanceId] = it }
     }
@@ -213,7 +201,11 @@
     ) {
         if (immediatelyRemove || isReorderingAllowed()) {
             interactor.dismissSmartspaceRecommendation(commonModel.recsLoadingModel.key, 0L)
-            // TODO if not immediate remove update host visibility
+            if (!immediatelyRemove) {
+                // Although it wasn't requested, we were able to process the removal
+                // immediately since reordering is allowed. So, notify hosts to update
+                updateHostVisibility()
+            }
         } else {
             modelsPendingRemoval.add(commonModel)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt
index aeaa82e..a96d75c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt
@@ -32,6 +32,7 @@
         override val onAdded: (MediaCommonViewModel) -> Unit,
         override val onRemoved: (Boolean) -> Unit,
         override val onUpdated: (MediaCommonViewModel) -> Unit,
+        val isMediaFromRec: Boolean = false,
     ) : MediaCommonViewModel()
 
     data class MediaRecommendations(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
index bc364c3..1944f07 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
@@ -20,6 +20,7 @@
 import android.content.pm.PackageManager
 import android.media.session.MediaController
 import android.media.session.MediaSession.Token
+import android.media.session.PlaybackState
 import android.text.TextUtils
 import android.util.Log
 import androidx.constraintlayout.widget.ConstraintSet
@@ -40,16 +41,14 @@
 import com.android.systemui.monet.ColorScheme
 import com.android.systemui.monet.Style
 import com.android.systemui.res.R
-import com.android.systemui.util.kotlin.sample
 import java.util.concurrent.Executor
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
 
 /** Models UI state and handles user input for a media control. */
 class MediaControlViewModel(
@@ -60,31 +59,20 @@
     private val logger: MediaUiEventLogger,
 ) {
 
-    private val isAnyButtonClicked: MutableStateFlow<Boolean> = MutableStateFlow(false)
-
-    private val playTurbulenceNoise: Flow<Boolean> =
-        interactor.mediaControl.sample(
-            combine(isAnyButtonClicked, interactor.isStartedPlaying) {
-                    isButtonClicked,
-                    isStartedPlaying ->
-                    isButtonClicked && isStartedPlaying
-                }
-                .distinctUntilChanged()
-        )
-
     @OptIn(ExperimentalCoroutinesApi::class)
     val player: Flow<MediaPlayerViewModel?> =
         interactor.onAnyMediaConfigurationChange
             .flatMapLatest {
-                combine(playTurbulenceNoise, interactor.mediaControl) {
-                    playTurbulenceNoise,
-                    mediaControl ->
-                    mediaControl?.let { toViewModel(it, playTurbulenceNoise) }
+                interactor.mediaControl.map { mediaControl ->
+                    mediaControl?.let { toViewModel(it) }
                 }
             }
             .distinctUntilChanged()
             .flowOn(backgroundDispatcher)
 
+    private var isPlaying = false
+    private var isAnyButtonClicked = false
+
     private fun onDismissMediaData(
         token: Token?,
         uid: Int,
@@ -95,10 +83,8 @@
         interactor.removeMediaControl(token, instanceId, MEDIA_PLAYER_ANIMATION_DELAY)
     }
 
-    private suspend fun toViewModel(
-        model: MediaControlModel,
-        playTurbulenceNoise: Boolean
-    ): MediaPlayerViewModel? {
+    private suspend fun toViewModel(model: MediaControlModel): MediaPlayerViewModel? {
+        val mediaController = model.token?.let { MediaController(applicationContext, it) }
         val wallpaperColors =
             MediaArtworkHelper.getWallpaperColor(
                 applicationContext,
@@ -118,8 +104,14 @@
 
         val gutsViewModel = toGutsViewModel(model, scheme)
 
+        // Set playing state
+        val wasPlaying = isPlaying
+        isPlaying =
+            mediaController?.playbackState?.let { it.state == PlaybackState.STATE_PLAYING } ?: false
+
         // Resetting button clicks state.
-        isAnyButtonClicked.value = false
+        val wasButtonClicked = isAnyButtonClicked
+        isAnyButtonClicked = false
 
         return MediaPlayerViewModel(
             contentDescription = { gutsVisible ->
@@ -144,7 +136,7 @@
             shouldAddGradient = wallpaperColors != null,
             colorScheme = scheme,
             canShowTime = canShowScrubbingTimeViews(model.semanticActionButtons),
-            playTurbulenceNoise = playTurbulenceNoise,
+            playTurbulenceNoise = isPlaying && !wasPlaying && wasButtonClicked,
             useSemanticActions = model.semanticActionButtons != null,
             actionButtons = toActionViewModels(model),
             outputSwitcher = toOutputSwitcherViewModel(model),
@@ -168,9 +160,7 @@
                     seekBarViewModel.updateStaticProgress(model.resumeProgress)
                 } else {
                     backgroundExecutor.execute {
-                        seekBarViewModel.updateController(
-                            model.token?.let { MediaController(applicationContext, it) }
-                        )
+                        seekBarViewModel.updateController(mediaController)
                     }
                 }
             }
@@ -283,16 +273,17 @@
         )
     }
 
-    private fun toActionViewModels(model: MediaControlModel): List<MediaActionViewModel?> {
+    private fun toActionViewModels(model: MediaControlModel): List<MediaActionViewModel> {
         val semanticActionButtons =
             model.semanticActionButtons?.let { mediaButton ->
-                with(mediaButton) {
-                    val isScrubbingTimeEnabled = canShowScrubbingTimeViews(mediaButton)
-                    SEMANTIC_ACTIONS_ALL.map { buttonId ->
-                        getActionById(buttonId)?.let {
-                            toSemanticActionViewModel(model, it, buttonId, isScrubbingTimeEnabled)
-                        }
-                    }
+                val isScrubbingTimeEnabled = canShowScrubbingTimeViews(mediaButton)
+                SEMANTIC_ACTIONS_ALL.map { buttonId ->
+                    toSemanticActionViewModel(
+                        model,
+                        mediaButton.getActionById(buttonId),
+                        buttonId,
+                        isScrubbingTimeEnabled
+                    )
                 }
             }
         val notifActionButtons =
@@ -304,7 +295,7 @@
 
     private fun toSemanticActionViewModel(
         model: MediaControlModel,
-        mediaAction: MediaAction,
+        mediaAction: MediaAction?,
         buttonId: Int,
         canShowScrubbingTimeViews: Boolean
     ): MediaActionViewModel {
@@ -312,9 +303,9 @@
         val hideWhenScrubbing = SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.contains(buttonId)
         val shouldHideWhenScrubbing = canShowScrubbingTimeViews && hideWhenScrubbing
         return MediaActionViewModel(
-            icon = mediaAction.icon,
-            contentDescription = mediaAction.contentDescription,
-            background = mediaAction.background,
+            icon = mediaAction?.icon,
+            contentDescription = mediaAction?.contentDescription,
+            background = mediaAction?.background,
             isVisibleWhenScrubbing = !shouldHideWhenScrubbing,
             notVisibleValue =
                 if (
@@ -326,11 +317,11 @@
                     ConstraintSet.GONE
                 },
             showInCollapsed = showInCollapsed,
-            rebindId = mediaAction.rebindId,
+            rebindId = mediaAction?.rebindId,
             buttonId = buttonId,
-            isEnabled = mediaAction.action != null,
+            isEnabled = mediaAction?.action != null,
             onClicked = { id ->
-                mediaAction.action?.let {
+                mediaAction?.action?.let {
                     onButtonClicked(id, model.uid, model.packageName, model.instanceId, it)
                 }
             },
@@ -366,7 +357,7 @@
     ) {
         logger.logTapAction(id, uid, packageName, instanceId)
         // TODO (b/330897926) log smartspace card reported (SMARTSPACE_CARD_CLICK_EVENT)
-        isAnyButtonClicked.value = true
+        isAnyButtonClicked = true
         action.run()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt
index d1014e8..4334341 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt
@@ -35,7 +35,7 @@
     val canShowTime: Boolean,
     val playTurbulenceNoise: Boolean,
     val useSemanticActions: Boolean,
-    val actionButtons: List<MediaActionViewModel?>,
+    val actionButtons: List<MediaActionViewModel>,
     val outputSwitcher: MediaOutputSwitcherViewModel,
     val gutsMenu: GutsViewModel,
     val onClicked: (Expandable) -> Unit,
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
index 88a5f78..061e7ec 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
@@ -48,7 +48,7 @@
         }
 
         @Override
-        public void onMediaDataRemoved(@NonNull String key) {
+        public void onMediaDataRemoved(@NonNull String key, boolean userInitiated) {
             final boolean hasActiveMedia = mMediaDataManager.hasActiveMedia();
             if (DEBUG) {
                 Log.d(TAG, "onMediaDataRemoved(" + key + "), mAdded=" + mAdded + ", hasActiveMedia="
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
index 412c006..9265bfb 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
@@ -140,10 +140,11 @@
 
         val bitmapShader = bitmapShader ?: return
         val thumbnailData = thumbnailData ?: return
+        val thumbnail = thumbnailData.thumbnail ?: return
         val display = context.display ?: return
         val windowMetrics = windowManager.maximumWindowMetrics
 
-        previewRect.set(0, 0, thumbnailData.thumbnail.width, thumbnailData.thumbnail.height)
+        previewRect.set(0, 0, thumbnail.width, thumbnail.height)
 
         val currentRotation: Int = display.rotation
         val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
diff --git a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
index 89e4760..a144dc2 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags
 import dagger.Lazy
 import javax.inject.Inject
 
@@ -48,7 +49,7 @@
      * Returns an override value for the given [flag] or `null` if the scene framework isn't enabled
      * or if the flag value doesn't need to be overridden.
      */
-    fun flagValueOverride(flag: Int): Boolean? {
+    fun flagValueOverride(@SystemUiStateFlags flag: Long): Boolean? {
         if (!SceneContainerFlag.isEnabled) {
             return null
         }
@@ -79,7 +80,7 @@
          * to be overridden by the scene framework.
          */
         val EvaluatorByFlag =
-            mapOf<Int, (SceneContainerPluginState) -> Boolean>(
+            mapOf<Long, (SceneContainerPluginState) -> Boolean>(
                 SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to { it.scene != Scenes.Gone },
                 SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to
                     {
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
index 2dd2327..67fe0e9 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
@@ -23,6 +23,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
 
 import dalvik.annotation.optimization.NeverCompile;
 
@@ -42,10 +43,10 @@
 
     private final DisplayTracker mDisplayTracker;
     private final SceneContainerPlugin mSceneContainerPlugin;
-    private @QuickStepContract.SystemUiStateFlags int mFlags;
+    private @SystemUiStateFlags long mFlags;
     private final List<SysUiStateCallback> mCallbacks = new ArrayList<>();
-    private int mFlagsToSet = 0;
-    private int mFlagsToClear = 0;
+    private long mFlagsToSet = 0;
+    private long mFlagsToClear = 0;
 
     public SysUiState(DisplayTracker displayTracker, SceneContainerPlugin sceneContainerPlugin) {
         mDisplayTracker = displayTracker;
@@ -67,12 +68,17 @@
     }
 
     /** Returns the current sysui state flags. */
-    public int getFlags() {
+    @SystemUiStateFlags
+    public long getFlags() {
         return mFlags;
     }
 
+    public boolean isFlagEnabled(@SystemUiStateFlags long flag) {
+        return (mFlags & flag) != 0;
+    }
+
     /** Methods to this call can be chained together before calling {@link #commitUpdate(int)}. */
-    public SysUiState setFlag(int flag, boolean enabled) {
+    public SysUiState setFlag(@SystemUiStateFlags long flag, boolean enabled) {
         final Boolean overrideOrNull = mSceneContainerPlugin.flagValueOverride(flag);
         if (overrideOrNull != null && enabled != overrideOrNull) {
             if (DEBUG) {
@@ -91,7 +97,7 @@
         return this;
     }
 
-    /** Call to save all the flags updated from {@link #setFlag(int, boolean)}. */
+    /** Call to save all the flags updated from {@link #setFlag(long, boolean)}. */
     public void commitUpdate(int displayId) {
         updateFlags(displayId);
         mFlagsToSet = 0;
@@ -105,14 +111,14 @@
             return;
         }
 
-        int newState = mFlags;
+        long newState = mFlags;
         newState |= mFlagsToSet;
         newState &= ~mFlagsToClear;
         notifyAndSetSystemUiStateChanged(newState, mFlags);
     }
 
     /** Notify all those who are registered that the state has changed. */
-    private void notifyAndSetSystemUiStateChanged(int newFlags, int oldFlags) {
+    private void notifyAndSetSystemUiStateChanged(long newFlags, long oldFlags) {
         if (DEBUG) {
             Log.d(TAG, "SysUiState changed: old=" + oldFlags + " new=" + newFlags);
         }
@@ -137,7 +143,7 @@
     /** Callback to be notified whenever system UI state flags are changed. */
     public interface SysUiStateCallback{
         /** To be called when any SysUiStateFlag gets updated */
-        void onSystemUiStateChanged(@QuickStepContract.SystemUiStateFlags int sysUiFlags);
+        void onSystemUiStateChanged(@SystemUiStateFlags long sysUiFlags);
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt b/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt
index 5c49156..1e18f24 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt
@@ -40,7 +40,7 @@
  */
 fun SysUiState.updateFlags(
     @DisplayId displayId: Int,
-    vararg flagValuePairs: Pair<Int, Boolean>,
+    vararg flagValuePairs: Pair<Long, Boolean>,
 ) {
     flagValuePairs.forEach { (flag, enabled) -> setFlag(flag, enabled) }
     commitUpdate(displayId)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index a6b6d61..80c4379 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -128,7 +128,7 @@
     private boolean mLongPressHomeEnabled;
     private boolean mAssistantTouchGestureEnabled;
     private int mNavBarMode;
-    private int mA11yButtonState;
+    private long mA11yButtonState;
     private int mRotationWatcherRotation;
     private boolean mTogglingNavbarTaskbar;
     private boolean mWallpaperVisible;
@@ -374,7 +374,7 @@
      * {@link Secure#ACCESSIBILITY_BUTTON_MODE_GESTURE}, otherwise it is reset to 0.
      */
     private void updateA11yState() {
-        final int prevState = mA11yButtonState;
+        final long prevState = mA11yButtonState;
         final boolean clickable;
         final boolean longClickable;
         if (mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()
@@ -431,7 +431,7 @@
      * 48 = the combination of {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_CLICKABLE} and
      * {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE}
      */
-    public int getA11yButtonState() {
+    public long getA11yButtonState() {
         return mA11yButtonState;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 906ebad..0e819c2 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -1602,7 +1602,7 @@
     void updateAccessibilityStateFlags() {
         mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled();
         if (mView != null) {
-            int a11yFlags = mNavBarHelper.getA11yButtonState();
+            long a11yFlags = mNavBarHelper.getA11yButtonState();
             boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
             boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
             mView.setAccessibilityButtonState(clickable, longClickable);
@@ -1611,7 +1611,7 @@
     }
 
     public void updateSystemUiStateFlags() {
-        int a11yFlags = mNavBarHelper.getA11yButtonState();
+        long a11yFlags = mNavBarHelper.getA11yButtonState();
         boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
         boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index f67973b..b360af0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -298,7 +298,7 @@
     }
 
     private void updateSysuiFlags() {
-        int a11yFlags = mNavBarHelper.getA11yButtonState();
+        long a11yFlags = mNavBarHelper.getA11yButtonState();
         boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
         boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 933065b..9487085 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -84,6 +84,7 @@
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.shared.system.InputMonitorCompat;
 import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -270,7 +271,8 @@
     private BackAnimation mBackAnimation;
     private int mLeftInset;
     private int mRightInset;
-    private int mSysUiFlags;
+    @SystemUiStateFlags
+    private long mSysUiFlags;
 
     // For Tf-Lite model.
     private BackGestureTfClassifierProvider mBackGestureTfClassifierProvider;
@@ -334,7 +336,7 @@
     private final SysUiState.SysUiStateCallback mSysUiStateCallback =
             new SysUiState.SysUiStateCallback() {
         @Override
-        public void onSystemUiStateChanged(int sysUiFlags) {
+        public void onSystemUiStateChanged(@SystemUiStateFlags long sysUiFlags) {
             mSysUiFlags = sysUiFlags;
         }
     };
@@ -442,7 +444,7 @@
                                         | PackageManager.MATCH_DISABLED_COMPONENTS
                                         | PackageManager.GET_SHARED_LIBRARY_FILES));
                 int resId = resources.getIdentifier(
-                        "gesture_blocking_activities", "array", recentsPackageName);
+                        "back_gesture_blocking_activities", "array", recentsPackageName);
 
                 if (resId == 0) {
                     Log.e(TAG, "No resource found for gesture-blocking activities");
diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
index 3907a72..5e6ee4d 100644
--- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
@@ -16,12 +16,20 @@
 
 package com.android.systemui.qrcodescanner.dagger
 
+import com.android.systemui.Flags
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.qs.tiles.QRCodeScannerTile
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileDataInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.impl.qr.ui.QRCodeScannerTileMapper
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel
 import com.android.systemui.res.R
 import dagger.Binds
 import dagger.Module
@@ -54,5 +62,24 @@
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
             )
+
+        /** Inject QR Code Scanner Tile into tileViewModelMap in QSModule. */
+        @Provides
+        @IntoMap
+        @StringKey(QR_CODE_SCANNER_TILE_SPEC)
+        fun provideQRCodeScannerTileViewModel(
+            factory: QSTileViewModelFactory.Static<QRCodeScannerTileModel>,
+            mapper: QRCodeScannerTileMapper,
+            stateInteractor: QRCodeScannerTileDataInteractor,
+            userActionInteractor: QRCodeScannerTileUserActionInteractor
+        ): QSTileViewModel =
+            if (Flags.qsNewTilesFuture())
+                factory.create(
+                    TileSpec.create(QR_CODE_SCANNER_TILE_SPEC),
+                    userActionInteractor,
+                    stateInteractor,
+                    mapper,
+                )
+            else StubQSTileViewModel
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
index 6fb5174..5720f76 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
@@ -125,7 +125,10 @@
 
     public void setNumPages(int numPages) {
         setVisibility(numPages > 1 ? View.VISIBLE : View.GONE);
-        if (numPages == getChildCount()) {
+        int childCount = getChildCount();
+        // We're checking if the width needs to be updated as it's possible that the number of pages
+        // was changed while the page indicator was not visible, automatically skipping onMeasure.
+        if (numPages == childCount && calculateWidth(childCount) == getMeasuredWidth()) {
             return;
         }
         if (mAnimating) {
@@ -295,6 +298,10 @@
         }
     }
 
+    private int calculateWidth(int numPages) {
+        return (mPageIndicatorWidth - mPageDotWidth) * (numPages - 1) + mPageDotWidth;
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         final int N = getChildCount();
@@ -309,7 +316,7 @@
         for (int i = 0; i < N; i++) {
             getChildAt(i).measure(widthChildSpec, heightChildSpec);
         }
-        int width = (mPageIndicatorWidth - mPageDotWidth) * (N - 1) + mPageDotWidth;
+        int width = calculateWidth(N);
         setMeasuredDimension(width, mPageIndicatorHeight);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt
index 2c8a5a4..1336d64 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt
@@ -18,6 +18,7 @@
 
 import android.service.quicksettings.Tile
 import android.text.TextUtils
+import android.widget.Switch
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.qs.external.CustomTile
 import com.android.systemui.qs.nano.QsTileState
@@ -44,8 +45,8 @@
         }
     label?.let { state.label = it.toString() }
     secondaryLabel?.let { state.secondaryLabel = it.toString() }
-    if (this is QSTile.BooleanState) {
-        state.booleanState = value
+    if (expandedAccessibilityClassName == Switch::class.java.name) {
+        state.booleanState = state.state == QsTileState.ACTIVE
     }
     return state
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index b705a03..ea89be6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.os.Handler;
 
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogModule;
 import com.android.systemui.dagger.NightDisplayListenerModule;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -60,6 +61,7 @@
  */
 @Module(subcomponents = {QSFragmentComponent.class, QSSceneComponent.class},
         includes = {
+                BluetoothTileDialogModule.class,
                 MediaModule.class,
                 PanelsModule.class,
                 QSExternalModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index d26ae0a..5d35a69 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -42,6 +42,7 @@
 import android.util.Log;
 import android.view.IWindowManager;
 import android.view.WindowManagerGlobal;
+import android.widget.Button;
 import android.widget.Switch;
 
 import androidx.annotation.Nullable;
@@ -502,6 +503,8 @@
         if (state instanceof BooleanState) {
             state.expandedAccessibilityClassName = Switch.class.getName();
             ((BooleanState) state).value = (state.state == Tile.STATE_ACTIVE);
+        } else {
+            state.expandedAccessibilityClassName = Button.class.getName();
         }
 
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 2a726c2..24b7a01 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -19,6 +19,8 @@
 import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
 import static android.service.quicksettings.TileService.START_ACTIVITY_NEEDS_PENDING_INTENT;
 
+import static com.android.systemui.Flags.qsCustomTileClickGuaranteedBugFix;
+
 import android.app.ActivityManager;
 import android.app.compat.CompatChanges;
 import android.content.BroadcastReceiver;
@@ -88,6 +90,7 @@
     private static final int MSG_ON_REMOVED = 1;
     private static final int MSG_ON_CLICK = 2;
     private static final int MSG_ON_UNLOCK_COMPLETE = 3;
+    private static final int MSG_ON_STOP_LISTENING = 4;
 
     // Bind retry control.
     private static final int MAX_BIND_RETRIES = 5;
@@ -368,6 +371,16 @@
                 onUnlockComplete();
             }
         }
+        if (qsCustomTileClickGuaranteedBugFix()) {
+            if (queue.contains(MSG_ON_STOP_LISTENING)) {
+                if (mDebug) Log.d(TAG, "Handling pending onStopListening " + getComponent());
+                if (mListening) {
+                    onStopListening();
+                } else {
+                    Log.w(TAG, "Trying to stop listening when not listening " + getComponent());
+                }
+            }
+        }
         if (queue.contains(MSG_ON_REMOVED)) {
             if (mDebug) Log.d(TAG, "Handling pending onRemoved " + getComponent());
             if (mListening) {
@@ -586,10 +599,15 @@
 
     @Override
     public void onStopListening() {
-        if (mDebug) Log.d(TAG, "onStopListening " + getComponent());
-        mListening = false;
-        if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStopListening)) {
-            handleDeath();
+        if (qsCustomTileClickGuaranteedBugFix() && hasPendingClick()) {
+            Log.d(TAG, "Enqueue stop listening");
+            queueMessage(MSG_ON_STOP_LISTENING);
+        } else {
+            if (mDebug) Log.d(TAG, "onStopListening " + getComponent());
+            mListening = false;
+            if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStopListening)) {
+                handleDeath();
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index f8bf0a6..6bc5095 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -15,6 +15,8 @@
  */
 package com.android.systemui.qs.external;
 
+import static com.android.systemui.Flags.qsCustomTileClickGuaranteedBugFix;
+
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -37,6 +39,7 @@
 
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Manages the priority which lets {@link TileServices} make decisions about which tiles
@@ -72,6 +75,8 @@
     private boolean mPendingBind = true;
     private boolean mStarted = false;
 
+    private final AtomicBoolean mListeningFromRequest = new AtomicBoolean(false);
+
     TileServiceManager(TileServices tileServices, Handler handler, ComponentName component,
             UserTracker userTracker, TileLifecycleManager.Factory tileLifecycleManagerFactory,
             CustomTileAddedRepository customTileAddedRepository) {
@@ -159,15 +164,30 @@
         }
     }
 
+    void onStartListeningFromRequest() {
+        mListeningFromRequest.set(true);
+        mStateManager.onStartListening();
+    }
+
     public void setLastUpdate(long lastUpdate) {
         mLastUpdate = lastUpdate;
         if (mBound && isActiveTile()) {
-            mStateManager.onStopListening();
-            setBindRequested(false);
+            if (qsCustomTileClickGuaranteedBugFix()) {
+                if (mListeningFromRequest.compareAndSet(true, false)) {
+                    stopListeningAndUnbind();
+                }
+            } else {
+                stopListeningAndUnbind();
+            }
         }
         mServices.recalculateBindAllowance();
     }
 
+    private void stopListeningAndUnbind() {
+        mStateManager.onStopListening();
+        setBindRequested(false);
+    }
+
     public void handleDestroy() {
         setBindAllowed(false);
         mServices.getContext().unregisterReceiver(mUninstallReceiver);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 8278c79..d457e88 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -15,6 +15,8 @@
  */
 package com.android.systemui.qs.external;
 
+import static com.android.systemui.Flags.qsCustomTileClickGuaranteedBugFix;
+
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
@@ -222,9 +224,13 @@
                 return;
             }
             service.setBindRequested(true);
-            try {
-                service.getTileService().onStartListening();
-            } catch (RemoteException e) {
+            if (qsCustomTileClickGuaranteedBugFix()) {
+                service.onStartListeningFromRequest();
+            } else {
+                try {
+                    service.getTileService().onStartListening();
+                } catch (RemoteException e) {
+                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index b515ce0..278352c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
 import com.android.systemui.log.core.LogLevel.VERBOSE
 import com.android.systemui.log.dagger.QSConfigLog
 import com.android.systemui.log.dagger.QSLog
@@ -56,6 +57,9 @@
     fun d(@CompileTimeConstant msg: String, arg: Any) {
         buffer.log(TAG, DEBUG, { str1 = arg.toString() }, { "$msg: $str1" })
     }
+    fun i(@CompileTimeConstant msg: String, arg: Any) {
+        buffer.log(TAG, INFO, { str1 = arg.toString() }, { "$msg: $str1" })
+    }
 
     fun logTileAdded(tileSpec: String) {
         buffer.log(TAG, DEBUG, { str1 = tileSpec }, { "[$str1] Tile added" })
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index e3ba36f..2cc3985 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -16,12 +16,23 @@
 
 package com.android.systemui.qs.panels.dagger
 
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
+import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepositoryImpl
 import com.android.systemui.qs.panels.data.repository.IconTilesRepository
 import com.android.systemui.qs.panels.data.repository.IconTilesRepositoryImpl
+import com.android.systemui.qs.panels.domain.interactor.GridTypeConsistencyInteractor
+import com.android.systemui.qs.panels.domain.interactor.InfiniteGridConsistencyInteractor
+import com.android.systemui.qs.panels.domain.interactor.NoopGridConsistencyInteractor
+import com.android.systemui.qs.panels.shared.model.GridConsistencyLog
 import com.android.systemui.qs.panels.shared.model.GridLayoutType
 import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
+import com.android.systemui.qs.panels.shared.model.StretchedGridLayoutType
 import com.android.systemui.qs.panels.ui.compose.GridLayout
 import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
+import com.android.systemui.qs.panels.ui.compose.StretchedGridLayout
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
@@ -31,18 +42,71 @@
 interface PanelsModule {
     @Binds fun bindIconTilesRepository(impl: IconTilesRepositoryImpl): IconTilesRepository
 
+    @Binds
+    fun bindGridLayoutTypeRepository(impl: GridLayoutTypeRepositoryImpl): GridLayoutTypeRepository
+
+    @Binds
+    fun bindDefaultGridConsistencyInteractor(
+        impl: NoopGridConsistencyInteractor
+    ): GridTypeConsistencyInteractor
+
     companion object {
         @Provides
+        @SysUISingleton
+        @GridConsistencyLog
+        fun providesGridConsistencyLog(factory: LogBufferFactory): LogBuffer {
+            return factory.create("GridConsistencyLog", 50)
+        }
+
+        @Provides
         @IntoSet
         fun provideGridLayout(gridLayout: InfiniteGridLayout): Pair<GridLayoutType, GridLayout> {
             return Pair(InfiniteGridLayoutType, gridLayout)
         }
 
         @Provides
+        @IntoSet
+        fun provideStretchedGridLayout(
+            gridLayout: StretchedGridLayout
+        ): Pair<GridLayoutType, GridLayout> {
+            return Pair(StretchedGridLayoutType, gridLayout)
+        }
+
+        @Provides
         fun provideGridLayoutMap(
             entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridLayout>>
         ): Map<GridLayoutType, GridLayout> {
             return entries.toMap()
         }
+
+        @Provides
+        fun provideGridLayoutTypes(
+            entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridLayout>>
+        ): Set<GridLayoutType> {
+            return entries.map { it.first }.toSet()
+        }
+
+        @Provides
+        @IntoSet
+        fun provideGridConsistencyInteractor(
+            consistencyInteractor: InfiniteGridConsistencyInteractor
+        ): Pair<GridLayoutType, GridTypeConsistencyInteractor> {
+            return Pair(InfiniteGridLayoutType, consistencyInteractor)
+        }
+
+        @Provides
+        @IntoSet
+        fun provideStretchedGridConsistencyInteractor(
+            consistencyInteractor: NoopGridConsistencyInteractor
+        ): Pair<GridLayoutType, GridTypeConsistencyInteractor> {
+            return Pair(StretchedGridLayoutType, consistencyInteractor)
+        }
+
+        @Provides
+        fun provideGridConsistencyInteractorMap(
+            entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridTypeConsistencyInteractor>>
+        ): Map<GridLayoutType, GridTypeConsistencyInteractor> {
+            return entries.toMap()
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
index 02dd33e..31795d5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
@@ -20,10 +20,23 @@
 import com.android.systemui.qs.panels.shared.model.GridLayoutType
 import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
 import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+interface GridLayoutTypeRepository {
+    val layout: StateFlow<GridLayoutType>
+    fun setLayout(type: GridLayoutType)
+}
 
 @SysUISingleton
-class GridLayoutTypeRepository @Inject constructor() {
-    val layout: Flow<GridLayoutType> = flowOf(InfiniteGridLayoutType)
+class GridLayoutTypeRepositoryImpl @Inject constructor() : GridLayoutTypeRepository {
+    private val _layout: MutableStateFlow<GridLayoutType> = MutableStateFlow(InfiniteGridLayoutType)
+    override val layout = _layout.asStateFlow()
+
+    override fun setLayout(type: GridLayoutType) {
+        if (_layout.value != type) {
+            _layout.value = type
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepository.kt
new file mode 100644
index 0000000..28c1fbf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepository.kt
@@ -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 com.android.systemui.qs.panels.data.repository
+
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.panels.shared.model.EditTileData
+import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+class IconAndNameCustomRepository
+@Inject
+constructor(
+    private val installedTilesComponentRepository: InstalledTilesComponentRepository,
+    private val userTracker: UserTracker,
+    @Background private val backgroundContext: CoroutineContext,
+) {
+    /**
+     * Returns a list of the icon/labels for all available (installed and enabled) tile services.
+     *
+     * No order is guaranteed.
+     */
+    suspend fun getCustomTileData(): List<EditTileData> {
+        return withContext(backgroundContext) {
+            val installedTiles =
+                installedTilesComponentRepository.getInstalledTilesServiceInfos(userTracker.userId)
+            val packageManager = userTracker.userContext.packageManager
+            installedTiles
+                .map {
+                    val tileSpec = TileSpec.create(it.componentName)
+                    val label = it.loadLabel(packageManager)
+                    val icon = it.loadIcon(packageManager)
+                    val appName = it.applicationInfo.loadLabel(packageManager)
+                    if (icon != null) {
+                        EditTileData(
+                            tileSpec,
+                            Icon.Loaded(icon, ContentDescription.Loaded(label.toString())),
+                            Text.Loaded(label.toString()),
+                            Text.Loaded(appName.toString()),
+                        )
+                    } else {
+                        null
+                    }
+                }
+                .filterNotNull()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
index 92f87e7..e581bfc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
@@ -19,20 +19,20 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 
 /** Repository for retrieving the list of [TileSpec] to be displayed as icons. */
 interface IconTilesRepository {
-    val iconTilesSpecs: Flow<Set<TileSpec>>
+    val iconTilesSpecs: StateFlow<Set<TileSpec>>
 }
 
 @SysUISingleton
 class IconTilesRepositoryImpl @Inject constructor() : IconTilesRepository {
 
-    /** Set of toggleable tiles that are suitable for being shown as an icon. */
-    override val iconTilesSpecs: Flow<Set<TileSpec>> =
-        flowOf(
+    private val _iconTilesSpecs =
+        MutableStateFlow(
             setOf(
                 TileSpec.create("airplane"),
                 TileSpec.create("battery"),
@@ -50,4 +50,7 @@
                 TileSpec.create("rotation")
             )
         )
+
+    /** Set of toggleable tiles that are suitable for being shown as an icon. */
+    override val iconTilesSpecs: StateFlow<Set<TileSpec>> = _iconTilesSpecs.asStateFlow()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt
new file mode 100644
index 0000000..43ccdf66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.qs.panels.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+class InfiniteGridSizeRepository @Inject constructor() {
+    // Number of columns in the narrowest state for consistency
+    private val _columns = MutableStateFlow(4)
+    val columns: StateFlow<Int> = _columns.asStateFlow()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/StockTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/StockTilesRepository.kt
new file mode 100644
index 0000000..ec9d151
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/StockTilesRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.qs.panels.data.repository
+
+import android.content.res.Resources
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+@SysUISingleton
+class StockTilesRepository
+@Inject
+constructor(
+    @Main private val resources: Resources,
+) {
+    /**
+     * List of stock platform tiles. All of the specs will be of type [TileSpec.PlatformTileSpec].
+     */
+    val stockTiles =
+        resources
+            .getString(R.string.quick_settings_tiles_stock)
+            .split(",")
+            .map(TileSpec::create)
+            .filterNot { it is TileSpec.Invalid }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractor.kt
new file mode 100644
index 0000000..3b29422
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractor.kt
@@ -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 com.android.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.data.repository.IconAndNameCustomRepository
+import com.android.systemui.qs.panels.data.repository.StockTilesRepository
+import com.android.systemui.qs.panels.domain.model.EditTilesModel
+import com.android.systemui.qs.panels.shared.model.EditTileData
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
+import javax.inject.Inject
+
+@SysUISingleton
+class EditTilesListInteractor
+@Inject
+constructor(
+    private val stockTilesRepository: StockTilesRepository,
+    private val qsTileConfigProvider: QSTileConfigProvider,
+    private val iconAndNameCustomRepository: IconAndNameCustomRepository,
+) {
+    /**
+     * Provides a list of the tiles to edit, with their UI information (icon, labels).
+     *
+     * The icons have the label as their content description.
+     */
+    suspend fun getTilesToEdit(): EditTilesModel {
+        val stockTiles =
+            stockTilesRepository.stockTiles.map {
+                if (qsTileConfigProvider.hasConfig(it.spec)) {
+                    val config = qsTileConfigProvider.getConfig(it.spec)
+                    EditTileData(
+                        it,
+                        Icon.Resource(
+                            config.uiConfig.iconRes,
+                            ContentDescription.Resource(config.uiConfig.labelRes)
+                        ),
+                        Text.Resource(config.uiConfig.labelRes),
+                        null,
+                    )
+                } else {
+                    EditTileData(
+                        it,
+                        Icon.Resource(
+                            android.R.drawable.star_on,
+                            ContentDescription.Loaded(it.spec)
+                        ),
+                        Text.Loaded(it.spec),
+                        null
+                    )
+                }
+            }
+        return EditTilesModel(stockTiles, iconAndNameCustomRepository.getCustomTileData())
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt
new file mode 100644
index 0000000..7732092
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.qs.panels.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.qs.panels.shared.model.GridConsistencyLog
+import com.android.systemui.qs.panels.shared.model.GridLayoutType
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class GridConsistencyInteractor
+@Inject
+constructor(
+    private val gridLayoutTypeInteractor: GridLayoutTypeInteractor,
+    private val currentTilesInteractor: CurrentTilesInteractor,
+    private val consistencyInteractors:
+        Map<GridLayoutType, @JvmSuppressWildcards GridTypeConsistencyInteractor>,
+    private val defaultConsistencyInteractor: GridTypeConsistencyInteractor,
+    @GridConsistencyLog private val logBuffer: LogBuffer,
+    @Application private val applicationScope: CoroutineScope,
+) {
+    fun start() {
+        applicationScope.launch {
+            gridLayoutTypeInteractor.layout.collectLatest { type ->
+                val consistencyInteractor =
+                    consistencyInteractors[type] ?: defaultConsistencyInteractor
+                currentTilesInteractor.currentTiles
+                    .map { tiles -> tiles.map { it.spec } }
+                    .collectLatest { tiles ->
+                        val newTiles = consistencyInteractor.reconcileTiles(tiles)
+                        if (newTiles != tiles) {
+                            currentTilesInteractor.setTiles(newTiles)
+                            logChange(newTiles)
+                        }
+                    }
+            }
+        }
+    }
+
+    private fun logChange(tiles: List<TileSpec>) {
+        logBuffer.log(
+            LOG_BUFFER_CURRENT_TILES_CHANGE_TAG,
+            LogLevel.DEBUG,
+            { str1 = tiles.toString() },
+            { "Tiles reordered: $str1" }
+        )
+    }
+
+    private companion object {
+        const val LOG_BUFFER_CURRENT_TILES_CHANGE_TAG = "GridConsistencyTilesChange"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt
index b6be578..4af1b22 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt
@@ -20,9 +20,13 @@
 import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
 import com.android.systemui.qs.panels.shared.model.GridLayoutType
 import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
 
 @SysUISingleton
-class GridLayoutTypeInteractor @Inject constructor(repo: GridLayoutTypeRepository) {
-    val layout: Flow<GridLayoutType> = repo.layout
+class GridLayoutTypeInteractor @Inject constructor(private val repo: GridLayoutTypeRepository) {
+    val layout: StateFlow<GridLayoutType> = repo.layout
+
+    fun setLayoutType(type: GridLayoutType) {
+        repo.setLayout(type)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt
index bf22563..4cdabae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.domain.interactor
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.qs.pipeline.shared.TileSpec
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
+interface GridTypeConsistencyInteractor {
+    /**
+     * Given a list of tiles, return the best list of the same tiles (preserving as much order as
+     * possible, such that it's consistent with the current layout.
+     */
+    fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec>
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
index 1aec193..ccc1c6e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
@@ -20,10 +20,10 @@
 import com.android.systemui.qs.panels.data.repository.IconTilesRepository
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
 
 /** Interactor for retrieving the list of [TileSpec] to be displayed as icons. */
 @SysUISingleton
 class IconTilesInteractor @Inject constructor(repo: IconTilesRepository) {
-    val iconTilesSpecs: Flow<Set<TileSpec>> = repo.iconTilesSpecs
+    val iconTilesSpecs: StateFlow<Set<TileSpec>> = repo.iconTilesSpecs
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
new file mode 100644
index 0000000..b437f64
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.qs.panels.domain.interactor
+
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.TileRow
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+
+@SysUISingleton
+class InfiniteGridConsistencyInteractor
+@Inject
+constructor(
+    private val iconTilesInteractor: IconTilesInteractor,
+    private val gridSizeInteractor: InfiniteGridSizeInteractor
+) : GridTypeConsistencyInteractor {
+
+    /**
+     * Tries to fill in every columns of all rows (except the last row), potentially reordering
+     * tiles.
+     */
+    override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> {
+        val newTiles: MutableList<TileSpec> = mutableListOf()
+        val row = TileRow<TileSpec>(columns = gridSizeInteractor.columns.value)
+        val iconTilesSet = iconTilesInteractor.iconTilesSpecs.value
+        val tilesQueue =
+            ArrayDeque(
+                tiles.map {
+                    SizedTile(
+                        it,
+                        width =
+                            if (iconTilesSet.contains(it)) {
+                                1
+                            } else {
+                                2
+                            }
+                    )
+                }
+            )
+
+        while (tilesQueue.isNotEmpty()) {
+            if (row.isFull()) {
+                newTiles.addAll(row.tiles.map { it.tile })
+                row.clear()
+            }
+
+            val tile = tilesQueue.removeFirst()
+
+            // If the tile fits in the row, add it.
+            if (!row.maybeAddTile(tile)) {
+                // If the tile does not fit the row, find an icon tile to move.
+                // We'll try to either add an icon tile from the queue to complete the row, or
+                // remove an icon tile from the current row to free up space.
+
+                val iconTile: SizedTile<TileSpec>? = tilesQueue.firstOrNull { it.width == 1 }
+                if (iconTile != null) {
+                    tilesQueue.remove(iconTile)
+                    tilesQueue.addFirst(tile)
+                    row.maybeAddTile(iconTile)
+                } else {
+                    val tileToRemove: SizedTile<TileSpec>? = row.findLastIconTile()
+                    if (tileToRemove != null) {
+                        row.removeTile(tileToRemove)
+                        row.maybeAddTile(tile)
+
+                        // Moving the icon tile to the end because there's no other
+                        // icon tiles in the queue.
+                        tilesQueue.addLast(tileToRemove)
+                    } else {
+                        // If the row does not have an icon tile, add the incomplete row.
+                        // Note: this shouldn't happen because an icon tile is guaranteed to be in a
+                        // row that doesn't have enough space for a large tile.
+                        val tileSpecs = row.tiles.map { it.tile }
+                        Log.wtf(TAG, "Uneven row does not have an icon tile to remove: $tileSpecs")
+                        newTiles.addAll(tileSpecs)
+                        row.clear()
+                        tilesQueue.addFirst(tile)
+                    }
+                }
+            }
+        }
+
+        // Add last row that might be incomplete
+        newTiles.addAll(row.tiles.map { it.tile })
+
+        return newTiles.toList()
+    }
+
+    private companion object {
+        const val TAG = "InfiniteGridConsistencyInteractor"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractor.kt
similarity index 62%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractor.kt
index bf22563..13c6072 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractor.kt
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.domain.interactor
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.data.repository.InfiniteGridSizeRepository
 import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
+@SysUISingleton
+class InfiniteGridSizeInteractor @Inject constructor(repo: InfiniteGridSizeRepository) {
+    val columns: StateFlow<Int> = repo.columns
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopConsistencyInteractor.kt
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopConsistencyInteractor.kt
index bf22563..97ceacc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopConsistencyInteractor.kt
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.domain.interactor
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
+@SysUISingleton
+class NoopConsistencyInteractor @Inject constructor() : GridTypeConsistencyInteractor {
+    override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> = tiles
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt
new file mode 100644
index 0000000..0386a6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt
@@ -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.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+
+/** [GridTypeConsistencyInteractor] implementation that doesn't do any changes to tiles. */
+@SysUISingleton
+class NoopGridConsistencyInteractor @Inject constructor() : GridTypeConsistencyInteractor {
+    override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> = tiles
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/model/EditTilesModel.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/src/com/android/systemui/qs/panels/domain/model/EditTilesModel.kt
index bf22563..b573b9a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/model/EditTilesModel.kt
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.domain.model
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.qs.panels.shared.model.EditTileData
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+data class EditTilesModel(
+    val stockTiles: List<EditTileData>,
+    val customTiles: List<EditTileData>,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/EditTileData.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/EditTileData.kt
new file mode 100644
index 0000000..8b70bb9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/EditTileData.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.qs.panels.shared.model
+
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+data class EditTileData(
+    val tileSpec: TileSpec,
+    val icon: Icon,
+    val label: Text,
+    val appName: Text?,
+) {
+    init {
+        check(
+            (tileSpec is TileSpec.PlatformTileSpec && appName == null) ||
+                (tileSpec is TileSpec.CustomTileSpec && appName != null)
+        ) {
+            "tileSpec: $tileSpec - appName: $appName. " +
+                "appName must be non-null for custom tiles and only for custom tiles."
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt
index bf22563..884cde3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.shared.model
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import javax.inject.Qualifier
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class GridConsistencyLog()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt
index 23110dc..501730a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt
@@ -25,3 +25,9 @@
 
 /** Grid type representing a scrollable vertical grid. */
 data object InfiniteGridLayoutType : GridLayoutType
+
+/**
+ * Grid type representing a scrollable vertical grid where tiles will stretch to fill in empty
+ * spaces.
+ */
+data object StretchedGridLayoutType : GridLayoutType
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt
new file mode 100644
index 0000000..7e4381b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.qs.panels.shared.model
+
+/** Represents a tile of type [T] associated with a width */
+data class SizedTile<T>(val tile: T, val width: Int)
+
+/** Represents a row of [SizedTile] with a maximum width of [columns] */
+class TileRow<T>(private val columns: Int) {
+    private var availableColumns = columns
+    private val _tiles: MutableList<SizedTile<T>> = mutableListOf()
+    val tiles: List<SizedTile<T>>
+        get() = _tiles.toList()
+
+    fun maybeAddTile(tile: SizedTile<T>): Boolean {
+        if (availableColumns - tile.width >= 0) {
+            _tiles.add(tile)
+            availableColumns -= tile.width
+            return true
+        }
+        return false
+    }
+
+    fun findLastIconTile(): SizedTile<T>? {
+        return _tiles.findLast { it.width == 1 }
+    }
+
+    fun removeTile(tile: SizedTile<T>) {
+        _tiles.remove(tile)
+        availableColumns += tile.width
+    }
+
+    fun clear() {
+        _tiles.clear()
+        availableColumns = columns
+    }
+
+    fun isFull(): Boolean = availableColumns == 0
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt
new file mode 100644
index 0000000..3bda775
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.qs.panels.ui.compose
+
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.layout.Column
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel
+
+@Composable
+fun EditMode(
+    viewModel: EditModeViewModel,
+    modifier: Modifier = Modifier,
+) {
+    val gridLayout by viewModel.gridLayout.collectAsStateWithLifecycle()
+    val tiles by viewModel.tiles.collectAsStateWithLifecycle(emptyList())
+
+    BackHandler { viewModel.stopEditing() }
+
+    DisposableEffect(Unit) { onDispose { viewModel.stopEditing() } }
+
+    Column(modifier) {
+        gridLayout.EditTileGrid(
+            tiles,
+            Modifier,
+            viewModel::addTile,
+            viewModel::removeTile,
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt
index 68ce5d8..8806931 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt
@@ -18,7 +18,9 @@
 
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+import com.android.systemui.qs.pipeline.shared.TileSpec
 
 interface GridLayout {
     @Composable
@@ -26,4 +28,12 @@
         tiles: List<TileViewModel>,
         modifier: Modifier,
     )
+
+    @Composable
+    fun EditTileGrid(
+        tiles: List<EditTileViewModel>,
+        modifier: Modifier,
+        onAddTile: (TileSpec, Int) -> Unit,
+        onRemoveTile: (TileSpec) -> Unit,
+    )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
index e2143e0..f5ee720 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
@@ -16,64 +16,31 @@
 
 package com.android.systemui.qs.panels.ui.compose
 
-import android.graphics.drawable.Animatable
-import android.text.TextUtils
-import androidx.appcompat.content.res.AppCompatResources
-import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
-import androidx.compose.animation.graphics.res.animatedVectorResource
-import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
-import androidx.compose.animation.graphics.vector.AnimatedImageVector
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.basicMarquee
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.lazy.grid.GridCells
 import androidx.compose.foundation.lazy.grid.GridItemSpan
-import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.res.integerResource
-import com.android.compose.theme.colorAttr
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.ui.compose.Icon
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
-import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
+import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.toUiState
-import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.res.R
 import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.mapLatest
 
 @SysUISingleton
-class InfiniteGridLayout @Inject constructor(private val iconTilesInteractor: IconTilesInteractor) :
-    GridLayout {
+class InfiniteGridLayout
+@Inject
+constructor(
+    private val iconTilesInteractor: IconTilesInteractor,
+    private val gridSizeInteractor: InfiniteGridSizeInteractor
+) : GridLayout {
 
     @Composable
     override fun TileGrid(
@@ -85,20 +52,10 @@
             tiles.forEach { it.startListening(token) }
             onDispose { tiles.forEach { it.stopListening(token) } }
         }
-        val iconTilesSpecs by
-            iconTilesInteractor.iconTilesSpecs.collectAsState(initial = emptySet())
+        val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
+        val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
 
-        LazyVerticalGrid(
-            columns =
-                GridCells.Fixed(
-                    integerResource(R.integer.quick_settings_infinite_grid_num_columns)
-                ),
-            verticalArrangement =
-                Arrangement.spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)),
-            horizontalArrangement =
-                Arrangement.spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)),
-            modifier = modifier
-        ) {
+        TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
             items(
                 tiles.size,
                 span = { index ->
@@ -119,108 +76,23 @@
         }
     }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     @Composable
-    private fun Tile(
-        tile: TileViewModel,
-        iconOnly: Boolean,
+    override fun EditTileGrid(
+        tiles: List<EditTileViewModel>,
         modifier: Modifier,
+        onAddTile: (TileSpec, Int) -> Unit,
+        onRemoveTile: (TileSpec) -> Unit,
     ) {
-        val state: TileUiState by
-            tile.state
-                .mapLatest { it.toUiState() }
-                .collectAsState(initial = tile.currentState.toUiState())
-        val context = LocalContext.current
-        val horizontalAlignment =
-            if (iconOnly) {
-                Alignment.CenterHorizontally
-            } else {
-                Alignment.Start
-            }
+        val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
+        val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
 
-        Row(
-            modifier =
-                modifier
-                    .fillMaxWidth()
-                    .clip(RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)))
-                    .clickable { tile.onClick(null) }
-                    .background(colorAttr(state.colors.background))
-                    .padding(
-                        horizontal = dimensionResource(id = R.dimen.qs_label_container_margin)
-                    ),
-            verticalAlignment = Alignment.CenterVertically,
-            horizontalArrangement =
-                Arrangement.spacedBy(
-                    space = dimensionResource(id = R.dimen.qs_label_container_margin),
-                    alignment = horizontalAlignment
-                )
-        ) {
-            val icon =
-                remember(state.icon) {
-                    state.icon.get().let {
-                        if (it is QSTileImpl.ResourceIcon) {
-                            Icon.Resource(it.resId, null)
-                        } else {
-                            Icon.Loaded(it.getDrawable(context), null)
-                        }
-                    }
-                }
-            TileIcon(icon, colorAttr(state.colors.icon))
-
-            if (!iconOnly) {
-                Column(
-                    verticalArrangement = Arrangement.Center,
-                    modifier = Modifier.fillMaxHeight()
-                ) {
-                    Text(
-                        state.label.toString(),
-                        color = colorAttr(state.colors.label),
-                        modifier = Modifier.basicMarquee(),
-                    )
-                    if (!TextUtils.isEmpty(state.secondaryLabel)) {
-                        Text(
-                            state.secondaryLabel.toString(),
-                            color = colorAttr(state.colors.secondaryLabel),
-                            modifier = Modifier.basicMarquee(),
-                        )
-                    }
-                }
-            }
-        }
-    }
-
-    @OptIn(ExperimentalAnimationGraphicsApi::class)
-    @Composable
-    private fun TileIcon(icon: Icon, color: Color) {
-        val modifier = Modifier.size(dimensionResource(id = R.dimen.qs_icon_size))
-        val context = LocalContext.current
-        val loadedDrawable =
-            remember(icon, context) {
-                when (icon) {
-                    is Icon.Loaded -> icon.drawable
-                    is Icon.Resource -> AppCompatResources.getDrawable(context, icon.res)
-                }
-            }
-        if (loadedDrawable !is Animatable) {
-            Icon(
-                icon = icon,
-                tint = color,
-                modifier = modifier,
-            )
-        } else if (icon is Icon.Resource) {
-            val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
-            var atEnd by remember(icon.res) { mutableStateOf(false) }
-            LaunchedEffect(key1 = icon.res) {
-                delay(350)
-                atEnd = true
-            }
-            val painter = rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd)
-            Image(
-                painter = painter,
-                contentDescription = null,
-                colorFilter = ColorFilter.tint(color = color),
-                modifier = modifier
-            )
-        }
+        DefaultEditTileGrid(
+            tiles = tiles,
+            iconOnlySpecs = iconOnlySpecs,
+            columns = GridCells.Fixed(columns),
+            modifier = modifier,
+            onAddTile = onAddTile,
+            onRemoveTile = onRemoveTile,
+        )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
new file mode 100644
index 0000000..ddd97c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.qs.panels.ui.compose
+
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.dimensionResource
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
+import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.TileRow
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+@SysUISingleton
+class StretchedGridLayout
+@Inject
+constructor(
+    private val iconTilesInteractor: IconTilesInteractor,
+    private val gridSizeInteractor: InfiniteGridSizeInteractor,
+) : GridLayout {
+
+    @Composable
+    override fun TileGrid(
+        tiles: List<TileViewModel>,
+        modifier: Modifier,
+    ) {
+        DisposableEffect(tiles) {
+            val token = Any()
+            tiles.forEach { it.startListening(token) }
+            onDispose { tiles.forEach { it.stopListening(token) } }
+        }
+
+        // Tile widths [normal|stretched]
+        // Icon [3 | 4]
+        // Large [6 | 8]
+        val columns = 12
+        val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
+        val stretchedTiles =
+            remember(tiles) {
+                val sizedTiles =
+                    tiles.map {
+                        SizedTile(
+                            it,
+                            if (iconTilesSpecs.contains(it.spec)) {
+                                3
+                            } else {
+                                6
+                            }
+                        )
+                    }
+                splitInRows(sizedTiles, columns)
+            }
+
+        TileLazyGrid(columns = GridCells.Fixed(columns), modifier = modifier) {
+            items(stretchedTiles.size, span = { GridItemSpan(stretchedTiles[it].width) }) { index ->
+                Tile(
+                    stretchedTiles[index].tile,
+                    iconTilesSpecs.contains(stretchedTiles[index].tile.spec),
+                    Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
+                )
+            }
+        }
+    }
+
+    @Composable
+    override fun EditTileGrid(
+        tiles: List<EditTileViewModel>,
+        modifier: Modifier,
+        onAddTile: (TileSpec, Int) -> Unit,
+        onRemoveTile: (TileSpec) -> Unit
+    ) {
+        val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
+        val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
+
+        DefaultEditTileGrid(
+            tiles = tiles,
+            iconOnlySpecs = iconOnlySpecs,
+            columns = GridCells.Fixed(columns),
+            modifier = modifier,
+            onAddTile = onAddTile,
+            onRemoveTile = onRemoveTile,
+        )
+    }
+
+    private fun splitInRows(
+        tiles: List<SizedTile<TileViewModel>>,
+        columns: Int
+    ): List<SizedTile<TileViewModel>> {
+        val row = TileRow<TileViewModel>(columns)
+
+        return buildList {
+            for (tile in tiles) {
+                if (row.maybeAddTile(tile)) {
+                    if (row.isFull()) {
+                        // Row is full, no need to stretch tiles
+                        addAll(row.tiles)
+                        row.clear()
+                    }
+                } else {
+                    if (row.isFull()) {
+                        addAll(row.tiles)
+                    } else {
+                        // Stretching tiles when row isn't full
+                        addAll(row.tiles.map { it.copy(width = it.width + (it.width / 3)) })
+                    }
+                    row.clear()
+                    row.maybeAddTile(tile)
+                }
+            }
+            addAll(row.tiles)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
new file mode 100644
index 0000000..eb45110
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -0,0 +1,403 @@
+/*
+ * 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.qs.panels.ui.compose
+
+import android.graphics.drawable.Animatable
+import android.text.TextUtils
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
+import androidx.compose.animation.graphics.res.animatedVectorResource
+import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
+import androidx.compose.animation.graphics.vector.AnimatedImageVector
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.basicMarquee
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Arrangement.spacedBy
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.foundation.lazy.grid.LazyGridScope
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Remove
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.animation.Expandable
+import com.android.compose.theme.colorAttr
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.common.ui.compose.load
+import com.android.systemui.qs.panels.ui.viewmodel.ActiveTileColorAttributes
+import com.android.systemui.qs.panels.ui.viewmodel.AvailableEditActions
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.TileColorAttributes
+import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
+import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.toUiState
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.res.R
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.mapLatest
+
+object TileType
+
+@OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class)
+@Composable
+fun Tile(
+    tile: TileViewModel,
+    iconOnly: Boolean,
+    modifier: Modifier,
+) {
+    val state: TileUiState by
+        tile.state
+            .mapLatest { it.toUiState() }
+            .collectAsStateWithLifecycle(tile.currentState.toUiState())
+    val context = LocalContext.current
+
+    Expandable(
+        color = colorAttr(state.colors.background),
+        shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)),
+    ) {
+        Row(
+            modifier =
+                modifier
+                    .combinedClickable(
+                        onClick = { tile.onClick(it) },
+                        onLongClick = { tile.onLongClick(it) }
+                    )
+                    .tileModifier(state.colors),
+            verticalAlignment = Alignment.CenterVertically,
+            horizontalArrangement = tileHorizontalArrangement(iconOnly),
+        ) {
+            val icon =
+                remember(state.icon) {
+                    state.icon.get().let {
+                        if (it is QSTileImpl.ResourceIcon) {
+                            Icon.Resource(it.resId, null)
+                        } else {
+                            Icon.Loaded(it.getDrawable(context), null)
+                        }
+                    }
+                }
+            TileContent(
+                label = state.label.toString(),
+                secondaryLabel = state.secondaryLabel.toString(),
+                icon = icon,
+                colors = state.colors,
+                iconOnly = iconOnly
+            )
+        }
+    }
+}
+
+@Composable
+fun TileLazyGrid(
+    modifier: Modifier = Modifier,
+    columns: GridCells,
+    content: LazyGridScope.() -> Unit,
+) {
+    LazyVerticalGrid(
+        columns = columns,
+        verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)),
+        horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)),
+        modifier = modifier,
+        content = content,
+    )
+}
+
+@Composable
+fun DefaultEditTileGrid(
+    tiles: List<EditTileViewModel>,
+    iconOnlySpecs: Set<TileSpec>,
+    columns: GridCells,
+    modifier: Modifier,
+    onAddTile: (TileSpec, Int) -> Unit,
+    onRemoveTile: (TileSpec) -> Unit,
+) {
+    val (currentTiles, otherTiles) = tiles.partition { it.isCurrent }
+    val (otherTilesStock, otherTilesCustom) = otherTiles.partition { it.appName == null }
+    val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
+        onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
+    }
+    val isIconOnly: (TileSpec) -> Boolean =
+        remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } }
+
+    TileLazyGrid(modifier = modifier, columns = columns) {
+        // These Text are just placeholders to see the different sections. Not final UI.
+        item(span = { GridItemSpan(maxLineSpan) }) { Text("Current tiles", color = Color.White) }
+
+        editTiles(
+            currentTiles,
+            ClickAction.REMOVE,
+            onRemoveTile,
+            isIconOnly,
+            indicatePosition = true,
+        )
+
+        item(span = { GridItemSpan(maxLineSpan) }) { Text("Tiles to add", color = Color.White) }
+
+        editTiles(
+            otherTilesStock,
+            ClickAction.ADD,
+            addTileToEnd,
+            isIconOnly,
+        )
+
+        item(span = { GridItemSpan(maxLineSpan) }) {
+            Text("Custom tiles to add", color = Color.White)
+        }
+
+        editTiles(
+            otherTilesCustom,
+            ClickAction.ADD,
+            addTileToEnd,
+            isIconOnly,
+        )
+    }
+}
+
+private fun LazyGridScope.editTiles(
+    tiles: List<EditTileViewModel>,
+    clickAction: ClickAction,
+    onClick: (TileSpec) -> Unit,
+    isIconOnly: (TileSpec) -> Boolean,
+    indicatePosition: Boolean = false,
+) {
+    items(
+        count = tiles.size,
+        key = { tiles[it].tileSpec.spec },
+        span = { GridItemSpan(if (isIconOnly(tiles[it].tileSpec)) 1 else 2) },
+        contentType = { TileType }
+    ) {
+        val viewModel = tiles[it]
+        val canClick =
+            when (clickAction) {
+                ClickAction.ADD -> AvailableEditActions.ADD in viewModel.availableEditActions
+                ClickAction.REMOVE -> AvailableEditActions.REMOVE in viewModel.availableEditActions
+            }
+        val onClickActionName =
+            when (clickAction) {
+                ClickAction.ADD ->
+                    stringResource(id = R.string.accessibility_qs_edit_tile_add_action)
+                ClickAction.REMOVE ->
+                    stringResource(id = R.string.accessibility_qs_edit_remove_tile_action)
+            }
+        val stateDescription =
+            if (indicatePosition) {
+                stringResource(id = R.string.accessibility_qs_edit_position, it + 1)
+            } else {
+                ""
+            }
+
+        Box(
+            modifier =
+                Modifier.clickable(enabled = canClick) { onClick.invoke(viewModel.tileSpec) }
+                    .animateItem()
+                    .semantics {
+                        onClick(onClickActionName) { false }
+                        this.stateDescription = stateDescription
+                    }
+        ) {
+            EditTile(
+                tileViewModel = viewModel,
+                isIconOnly(viewModel.tileSpec),
+                modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
+            )
+            if (canClick) {
+                Badge(clickAction, Modifier.align(Alignment.TopEnd))
+            }
+        }
+    }
+}
+
+@Composable
+fun Badge(action: ClickAction, modifier: Modifier = Modifier) {
+    Box(modifier = modifier.size(16.dp).background(Color.Cyan, shape = CircleShape)) {
+        Icon(
+            imageVector =
+                when (action) {
+                    ClickAction.ADD -> Icons.Filled.Add
+                    ClickAction.REMOVE -> Icons.Filled.Remove
+                },
+            "",
+            tint = Color.Black,
+        )
+    }
+}
+
+@Composable
+fun EditTile(
+    tileViewModel: EditTileViewModel,
+    iconOnly: Boolean,
+    modifier: Modifier = Modifier,
+) {
+    val label = tileViewModel.label.load() ?: tileViewModel.tileSpec.spec
+    val colors = ActiveTileColorAttributes
+
+    Row(
+        modifier = modifier.tileModifier(colors).semantics { this.contentDescription = label },
+        verticalAlignment = Alignment.CenterVertically,
+        horizontalArrangement = tileHorizontalArrangement(iconOnly)
+    ) {
+        TileContent(
+            label = label,
+            secondaryLabel = tileViewModel.appName?.load(),
+            colors = colors,
+            icon = tileViewModel.icon,
+            iconOnly = iconOnly,
+            animateIconToEnd = true,
+        )
+    }
+}
+
+enum class ClickAction {
+    ADD,
+    REMOVE,
+}
+
+@OptIn(ExperimentalAnimationGraphicsApi::class)
+@Composable
+private fun TileIcon(
+    icon: Icon,
+    color: Color,
+    animateToEnd: Boolean = false,
+) {
+    val modifier = Modifier.size(dimensionResource(id = R.dimen.qs_icon_size))
+    val context = LocalContext.current
+    val loadedDrawable =
+        remember(icon, context) {
+            when (icon) {
+                is Icon.Loaded -> icon.drawable
+                is Icon.Resource -> AppCompatResources.getDrawable(context, icon.res)
+            }
+        }
+    if (loadedDrawable !is Animatable) {
+        Icon(
+            icon = icon,
+            tint = color,
+            modifier = modifier,
+        )
+    } else if (icon is Icon.Resource) {
+        val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
+        val painter =
+            if (animateToEnd) {
+                rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true)
+            } else {
+                var atEnd by remember(icon.res) { mutableStateOf(false) }
+                LaunchedEffect(key1 = icon.res) {
+                    delay(350)
+                    atEnd = true
+                }
+                rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd)
+            }
+        Image(
+            painter = painter,
+            contentDescription = null,
+            colorFilter = ColorFilter.tint(color = color),
+            modifier = modifier
+        )
+    }
+}
+
+@Composable
+private fun Modifier.tileModifier(colors: TileColorAttributes): Modifier {
+    return fillMaxWidth()
+        .clip(RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)))
+        .background(colorAttr(colors.background))
+        .padding(horizontal = dimensionResource(id = R.dimen.qs_label_container_margin))
+}
+
+@Composable
+private fun tileHorizontalArrangement(iconOnly: Boolean): Arrangement.Horizontal {
+    val horizontalAlignment =
+        if (iconOnly) {
+            Alignment.CenterHorizontally
+        } else {
+            Alignment.Start
+        }
+    return spacedBy(
+        space = dimensionResource(id = R.dimen.qs_label_container_margin),
+        alignment = horizontalAlignment
+    )
+}
+
+@Composable
+private fun TileContent(
+    label: String,
+    secondaryLabel: String?,
+    icon: Icon,
+    colors: TileColorAttributes,
+    iconOnly: Boolean,
+    animateIconToEnd: Boolean = false,
+) {
+    TileIcon(icon, colorAttr(colors.icon), animateIconToEnd)
+
+    if (!iconOnly) {
+        Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) {
+            Text(
+                label,
+                color = colorAttr(colors.label),
+                modifier = Modifier.basicMarquee(),
+            )
+            if (!TextUtils.isEmpty(secondaryLabel)) {
+                Text(
+                    secondaryLabel ?: "",
+                    color = colorAttr(colors.secondaryLabel),
+                    modifier = Modifier.basicMarquee(),
+                )
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt
index 2f32d72..2dab7c3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt
@@ -17,15 +17,15 @@
 package com.android.systemui.qs.panels.ui.compose
 
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel
 
 @Composable
 fun TileGrid(viewModel: TileGridViewModel, modifier: Modifier = Modifier) {
-    val gridLayout by viewModel.gridLayout.collectAsState()
-    val tiles by viewModel.tileViewModels.collectAsState(emptyList())
+    val gridLayout by viewModel.gridLayout.collectAsStateWithLifecycle()
+    val tiles by viewModel.tileViewModels.collectAsStateWithLifecycle(emptyList())
 
     gridLayout.TileGrid(tiles, modifier)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
new file mode 100644
index 0000000..69f50a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
@@ -0,0 +1,154 @@
+/*
+ * 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.qs.panels.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.qs.panels.domain.interactor.EditTilesListInteractor
+import com.android.systemui.qs.panels.domain.interactor.GridLayoutTypeInteractor
+import com.android.systemui.qs.panels.shared.model.GridLayoutType
+import com.android.systemui.qs.panels.ui.compose.GridLayout
+import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor.Companion.POSITION_AT_END
+import com.android.systemui.qs.pipeline.domain.interactor.MinimumTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+@SysUISingleton
+@OptIn(ExperimentalCoroutinesApi::class)
+class EditModeViewModel
+@Inject
+constructor(
+    private val editTilesListInteractor: EditTilesListInteractor,
+    private val currentTilesInteractor: CurrentTilesInteractor,
+    private val minTilesInteractor: MinimumTilesInteractor,
+    private val defaultGridLayout: InfiniteGridLayout,
+    @Application private val applicationScope: CoroutineScope,
+    gridLayoutTypeInteractor: GridLayoutTypeInteractor,
+    gridLayoutMap: Map<GridLayoutType, @JvmSuppressWildcards GridLayout>,
+) {
+    private val _isEditing = MutableStateFlow(false)
+
+    /**
+     * Whether we should be editing right now. Use [startEditing] and [stopEditing] to change this
+     */
+    val isEditing = _isEditing.asStateFlow()
+    private val minimumTiles: Int
+        get() = minTilesInteractor.minNumberOfTiles
+
+    val gridLayout: StateFlow<GridLayout> =
+        gridLayoutTypeInteractor.layout
+            .map { gridLayoutMap[it] ?: defaultGridLayout }
+            .stateIn(
+                applicationScope,
+                SharingStarted.WhileSubscribed(),
+                defaultGridLayout,
+            )
+
+    /**
+     * Flow of view models for each tile that should be visible in edit mode (or empty flow when not
+     * editing).
+     *
+     * Guarantees of the data:
+     * * The data for the tiles is fetched once whenever [isEditing] goes from `false` to `true`.
+     *   This prevents icons/labels changing while in edit mode.
+     * * It tracks the current tiles as they are added/removed/moved by the user.
+     * * The tiles that are current will be in the same relative order as the user sees them in
+     *   Quick Settings.
+     * * The tiles that are not current will preserve their relative order even when the current
+     *   tiles change.
+     */
+    val tiles =
+        isEditing.flatMapLatest {
+            if (it) {
+                val editTilesData = editTilesListInteractor.getTilesToEdit()
+                currentTilesInteractor.currentTiles.map { tiles ->
+                    val currentSpecs = tiles.map { it.spec }
+                    val canRemoveTiles = currentSpecs.size > minimumTiles
+                    val allTiles = editTilesData.stockTiles + editTilesData.customTiles
+                    val allTilesMap = allTiles.associate { it.tileSpec to it }
+                    val currentTiles = currentSpecs.map { allTilesMap.get(it) }.filterNotNull()
+                    val nonCurrentTiles = allTiles.filter { it.tileSpec !in currentSpecs }
+
+                    (currentTiles + nonCurrentTiles).map {
+                        val current = it.tileSpec in currentSpecs
+                        val availableActions = buildSet {
+                            if (current) {
+                                add(AvailableEditActions.MOVE)
+                                if (canRemoveTiles) {
+                                    add(AvailableEditActions.REMOVE)
+                                }
+                            } else {
+                                add(AvailableEditActions.ADD)
+                            }
+                        }
+                        EditTileViewModel(
+                            it.tileSpec,
+                            it.icon,
+                            it.label,
+                            it.appName,
+                            current,
+                            availableActions
+                        )
+                    }
+                }
+            } else {
+                emptyFlow()
+            }
+        }
+
+    /** @see isEditing */
+    fun startEditing() {
+        _isEditing.value = true
+    }
+
+    /** @see isEditing */
+    fun stopEditing() {
+        _isEditing.value = false
+    }
+
+    /** Immediately moves [tileSpec] to [position]. */
+    fun moveTile(tileSpec: TileSpec, position: Int) {
+        throw NotImplementedError("This is not supported yet")
+    }
+
+    /** Immediately adds [tileSpec] to the current tiles at [position]. */
+    fun addTile(tileSpec: TileSpec, position: Int = POSITION_AT_END) {
+        currentTilesInteractor.addTile(tileSpec, position)
+    }
+
+    /** Immediately removes [tileSpec] from the current tiles. */
+    fun removeTile(tileSpec: TileSpec) {
+        currentTilesInteractor.removeTiles(listOf(tileSpec))
+    }
+
+    /** Immediately resets the current tiles to the default list. */
+    fun resetCurrentTilesToDefault() {
+        throw NotImplementedError("This is not supported yet")
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt
new file mode 100644
index 0000000..ba9a044
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.qs.panels.ui.viewmodel
+
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+/**
+ * View model for each tile that is available to be added/removed/moved in Edit mode.
+ *
+ * [isCurrent] indicates whether this tile is part of the current set of tiles that the user sees in
+ * Quick Settings.
+ */
+class EditTileViewModel(
+    val tileSpec: TileSpec,
+    val icon: Icon,
+    val label: Text,
+    val appName: Text?,
+    val isCurrent: Boolean,
+    val availableEditActions: Set<AvailableEditActions>,
+)
+
+enum class AvailableEditActions {
+    ADD,
+    REMOVE,
+    MOVE,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
index cfcea98..c5b2737 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
@@ -23,6 +23,7 @@
 import android.content.Intent
 import android.content.pm.PackageManager
 import android.content.pm.PackageManager.ResolveInfoFlags
+import android.content.pm.ServiceInfo
 import android.os.UserHandle
 import android.service.quicksettings.TileService
 import androidx.annotation.GuardedBy
@@ -36,14 +37,17 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
 
 interface InstalledTilesComponentRepository {
 
     fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>>
+
+    fun getInstalledTilesServiceInfos(userId: Int): List<ServiceInfo>
 }
 
 @SysUISingleton
@@ -55,38 +59,45 @@
     private val packageChangeRepository: PackageChangeRepository
 ) : InstalledTilesComponentRepository {
 
-    @GuardedBy("userMap") private val userMap = mutableMapOf<Int, Flow<Set<ComponentName>>>()
+    @GuardedBy("userMap") private val userMap = mutableMapOf<Int, StateFlow<List<ServiceInfo>>>()
 
     override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> =
-        synchronized(userMap) {
-            userMap.getOrPut(userId) {
-                /*
-                 * In order to query [PackageManager] for different users, this implementation will
-                 * call [Context.createContextAsUser] and retrieve the [PackageManager] from that
-                 * context.
-                 */
-                val packageManager =
-                    if (applicationContext.userId == userId) {
-                        applicationContext.packageManager
-                    } else {
-                        applicationContext
-                            .createContextAsUser(
-                                UserHandle.of(userId),
-                                /* flags */ 0,
-                            )
-                            .packageManager
-                    }
-                packageChangeRepository
-                    .packageChanged(UserHandle.of(userId))
-                    .onStart { emit(PackageChangeModel.Empty) }
-                    .map { reloadComponents(userId, packageManager) }
-                    .distinctUntilChanged()
-                    .shareIn(backgroundScope, SharingStarted.WhileSubscribed(), replay = 1)
-            }
+        synchronized(userMap) { getForUserLocked(userId) }
+            .map { it.mapTo(mutableSetOf()) { it.componentName } }
+
+    override fun getInstalledTilesServiceInfos(userId: Int): List<ServiceInfo> {
+        return synchronized(userMap) { getForUserLocked(userId).value }
+    }
+
+    private fun getForUserLocked(userId: Int): StateFlow<List<ServiceInfo>> {
+        return userMap.getOrPut(userId) {
+            /*
+             * In order to query [PackageManager] for different users, this implementation will
+             * call [Context.createContextAsUser] and retrieve the [PackageManager] from that
+             * context.
+             */
+            val packageManager =
+                if (applicationContext.userId == userId) {
+                    applicationContext.packageManager
+                } else {
+                    applicationContext
+                        .createContextAsUser(
+                            UserHandle.of(userId),
+                            /* flags */ 0,
+                        )
+                        .packageManager
+                }
+            packageChangeRepository
+                .packageChanged(UserHandle.of(userId))
+                .onStart { emit(PackageChangeModel.Empty) }
+                .map { reloadComponents(userId, packageManager) }
+                .distinctUntilChanged()
+                .stateIn(backgroundScope, SharingStarted.WhileSubscribed(), emptyList())
         }
+    }
 
     @WorkerThread
-    private fun reloadComponents(userId: Int, packageManager: PackageManager): Set<ComponentName> {
+    private fun reloadComponents(userId: Int, packageManager: PackageManager): List<ServiceInfo> {
         return packageManager
             .queryIntentServicesAsUser(INTENT, FLAGS, userId)
             .mapNotNull { it.serviceInfo }
@@ -100,7 +111,6 @@
                     false
                 }
             }
-            .mapTo(mutableSetOf()) { it.componentName }
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index 61896f0..b7fcef4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -115,6 +115,10 @@
      * @see TileSpecRepository.setTiles
      */
     fun setTiles(specs: List<TileSpec>)
+
+    companion object {
+        val POSITION_AT_END: Int = TileSpecRepository.POSITION_AT_END
+    }
 }
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/MinimumTilesInteractor.kt
similarity index 65%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/MinimumTilesInteractor.kt
index bf22563..2ae3f07 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/MinimumTilesInteractor.kt
@@ -14,12 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.pipeline.domain.interactor
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.qs.pipeline.data.repository.MinimumTilesRepository
 import javax.inject.Inject
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
+class MinimumTilesInteractor
+@Inject
+constructor(
+    private val minimumTilesRepository: MinimumTilesRepository,
+) {
+    val minNumberOfTiles: Int
+        get() = minimumTilesRepository.minNumberOfTiles
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
index af1d195..c8fbeb5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
@@ -18,6 +18,8 @@
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.flags.NewQsUI
+import com.android.systemui.qs.panels.domain.interactor.GridConsistencyInteractor
 import com.android.systemui.qs.pipeline.domain.interactor.AccessibilityTilesInteractor
 import com.android.systemui.qs.pipeline.domain.interactor.AutoAddInteractor
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
@@ -34,6 +36,7 @@
     private val autoAddInteractor: AutoAddInteractor,
     private val featureFlags: QSPipelineFlagsRepository,
     private val restoreReconciliationInteractor: RestoreReconciliationInteractor,
+    private val gridConsistencyInteractor: GridConsistencyInteractor,
 ) : CoreStartable {
 
     override fun start() {
@@ -41,6 +44,10 @@
             accessibilityTilesInteractor.init(currentTilesInteractor)
             autoAddInteractor.init(currentTilesInteractor)
             restoreReconciliationInteractor.start()
+
+            if (NewQsUI.isEnabled) {
+                gridConsistencyInteractor.start()
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index c24113f1..56588ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -55,6 +55,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedLockUtilsInternal;
+import com.android.settingslib.graph.SignalDrawable;
 import com.android.systemui.Dumpable;
 import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.animation.Expandable;
@@ -632,12 +633,23 @@
     }
 
     public static class DrawableIcon extends Icon {
+
         protected final Drawable mDrawable;
         protected final Drawable mInvisibleDrawable;
+        private static final String TAG = "QSTileImpl";
 
         public DrawableIcon(Drawable drawable) {
             mDrawable = drawable;
-            mInvisibleDrawable = drawable.getConstantState().newDrawable();
+            Drawable.ConstantState nullableConstantState = drawable.getConstantState();
+            if (nullableConstantState == null) {
+                if (!(drawable instanceof SignalDrawable)) {
+                    Log.w(TAG, "DrawableIcon: drawable has null ConstantState"
+                            + " and is not a SignalDrawable");
+                }
+                mInvisibleDrawable = drawable;
+            } else {
+                mInvisibleDrawable = nullableConstantState.newDrawable();
+            }
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index f3852a2..4fd0df4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -186,7 +186,8 @@
     private val locInScreen = IntArray(2)
 
     /** Visuo-haptic long-press effects */
-    private var haveLongPressPropertiesBeenReset = true
+    var haveLongPressPropertiesBeenReset = true
+        private set
     private var paddingForLaunch = Rect()
     private var initialLongPressProperties: QSLongPressProperties? = null
     private var finalLongPressProperties: QSLongPressProperties? = null
@@ -772,7 +773,11 @@
         }
     }
 
-    override fun onActivityLaunchAnimationEnd() = resetLongPressEffectProperties()
+    override fun onActivityLaunchAnimationEnd() {
+        if (longPressEffect != null && !haveLongPressPropertiesBeenReset) {
+            resetLongPressEffectProperties()
+        }
+    }
 
     fun prepareForLaunch() {
         val startingHeight = initialLongPressProperties?.height?.toInt() ?: 0
@@ -877,8 +882,8 @@
         background.updateBounds(
             left = 0,
             top = 0,
-            right = initialLongPressProperties?.width?.toInt() ?: 0,
-            bottom = initialLongPressProperties?.height?.toInt() ?: 0,
+            right = initialLongPressProperties?.width?.toInt() ?: measuredWidth,
+            bottom = initialLongPressProperties?.height?.toInt() ?: measuredHeight,
         )
         changeCornerRadius(resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat())
         setAllColors(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 2068799..71b69c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs.tiles;
 
+import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_AIRPLANE_MODE;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -32,9 +34,12 @@
 import android.widget.Switch;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.telephony.flags.Flags;
+import com.android.settingslib.satellite.SatelliteDialogUtils;
 import com.android.systemui.animation.Expandable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -54,6 +59,8 @@
 
 import dagger.Lazy;
 
+import kotlinx.coroutines.Job;
+
 import javax.inject.Inject;
 
 /** Quick settings tile: Airplane mode **/
@@ -66,6 +73,9 @@
     private final Lazy<ConnectivityManager> mLazyConnectivityManager;
 
     private boolean mListening;
+    @Nullable
+    @VisibleForTesting
+    Job mClickJob;
 
     @Inject
     public AirplaneModeTile(
@@ -111,6 +121,21 @@
                     new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS), 0);
             return;
         }
+
+        if (Flags.oemEnabledSatelliteFlag()) {
+            if (mClickJob != null && !mClickJob.isCompleted()) {
+                return;
+            }
+            mClickJob = SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+                    mContext, this, TYPE_IS_AIRPLANE_MODE, isAllowClick -> {
+                        if (isAllowClick) {
+                            setEnabled(!airplaneModeEnabled);
+                        }
+                        return null;
+                    });
+            return;
+        }
+
         setEnabled(!airplaneModeEnabled);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 9af34f6..9f41d98 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles;
 
+import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_BLUETOOTH;
 import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
 
 import android.annotation.Nullable;
@@ -33,11 +34,14 @@
 import android.util.Log;
 import android.widget.Switch;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.Utils;
 import com.android.settingslib.bluetooth.BluetoothUtils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.satellite.SatelliteDialogUtils;
 import com.android.systemui.animation.Expandable;
 import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -55,6 +59,8 @@
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.BluetoothController;
 
+import kotlinx.coroutines.Job;
+
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -78,6 +84,9 @@
     private final BluetoothTileDialogViewModel mDialogViewModel;
 
     private final FeatureFlags mFeatureFlags;
+    @Nullable
+    @VisibleForTesting
+    Job mClickJob;
 
     @Inject
     public BluetoothTile(
@@ -110,6 +119,24 @@
 
     @Override
     protected void handleClick(@Nullable Expandable expandable) {
+        if (com.android.internal.telephony.flags.Flags.oemEnabledSatelliteFlag()) {
+            if (mClickJob != null && !mClickJob.isCompleted()) {
+                return;
+            }
+            mClickJob = SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+                    mContext, this, TYPE_IS_BLUETOOTH, isAllowClick -> {
+                        if (!isAllowClick) {
+                            return null;
+                        }
+                        handleClickEvent(expandable);
+                        return null;
+                    });
+            return;
+        }
+        handleClickEvent(expandable);
+    }
+
+    private void handleClickEvent(@Nullable Expandable expandable) {
         if (mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG)) {
             mDialogViewModel.showDialog(expandable);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
index 2d3120a..972b20e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
@@ -29,11 +29,15 @@
 
 /**
  * Provides a shortcut to start an activity from [QSTileUserActionInteractor]. It supports keyguard
- * dismissing and tile from-view animations.
+ * dismissing and tile from-view animations, as well as the option to show over lockscreen.
  */
 interface QSTileIntentUserInputHandler {
 
-    fun handle(expandable: Expandable?, intent: Intent)
+    fun handle(
+        expandable: Expandable?,
+        intent: Intent,
+        dismissShadeShowOverLockScreenWhenLocked: Boolean = false
+    )
 
     /** @param requestLaunchingDefaultActivity used in case !pendingIndent.isActivity */
     fun handle(
@@ -52,12 +56,25 @@
     private val userHandle: UserHandle,
 ) : QSTileIntentUserInputHandler {
 
-    override fun handle(expandable: Expandable?, intent: Intent) {
+    override fun handle(
+        expandable: Expandable?,
+        intent: Intent,
+        dismissShadeShowOverLockScreenWhenLocked: Boolean
+    ) {
         val animationController: ActivityTransitionAnimator.Controller? =
             expandable?.activityTransitionController(
                 InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
             )
-        activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController)
+        if (dismissShadeShowOverLockScreenWhenLocked) {
+            activityStarter.startActivity(
+                intent,
+                true /* dismissShade */,
+                animationController,
+                true /* showOverLockscreenWhenLocked */
+            )
+        } else {
+            activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController)
+        }
     }
 
     // TODO(b/249804373): make sure to allow showing activities over the lockscreen. See b/292112939
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
index 065e89f..f0d7206 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
@@ -175,6 +175,26 @@
             )
     }
 
+    /** Log with level [LogLevel.WARNING] */
+    fun logWarning(
+        tileSpec: TileSpec,
+        message: String,
+    ) {
+        tileSpec
+            .getLogBuffer()
+            .log(tileSpec.getLogTag(), LogLevel.WARNING, { str1 = message }, { str1!! })
+    }
+
+    /** Log with level [LogLevel.INFO] */
+    fun logInfo(
+        tileSpec: TileSpec,
+        message: String,
+    ) {
+        tileSpec
+            .getLogBuffer()
+            .log(tileSpec.getLogTag(), LogLevel.INFO, { str1 = message }, { str1!! })
+    }
+
     fun logCustomTileUserActionDelivered(tileSpec: TileSpec) {
         tileSpec
             .getLogBuffer()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
index ffa3b54..4c21080 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
@@ -25,10 +25,13 @@
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
 import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
 import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent
+import com.android.systemui.qs.tiles.impl.custom.di.QSTileConfigModule
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
 import com.android.systemui.qs.tiles.impl.di.QSTileComponent
-import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
@@ -47,7 +50,7 @@
      * binding them together. This achieves a DI scope that lives along the instance of
      * [QSTileViewModelImpl].
      */
-    class Component<T>
+    class Component
     @Inject
     constructor(
         private val disabledByPolicyInteractor: DisabledByPolicyInteractor,
@@ -58,7 +61,8 @@
         private val qsTileConfigProvider: QSTileConfigProvider,
         private val systemClock: SystemClock,
         @Background private val backgroundDispatcher: CoroutineDispatcher,
-    ) : QSTileViewModelFactory<T> {
+        private val customTileComponentBuilder: CustomTileComponent.Builder,
+    ) : QSTileViewModelFactory<CustomTileDataModel> {
 
         /**
          * Creates [QSTileViewModelImpl] based on the interactors obtained from [QSTileComponent].
@@ -66,10 +70,10 @@
          */
         fun create(
             tileSpec: TileSpec,
-            componentFactory: (config: QSTileConfig) -> QSTileComponent<T>
-        ): QSTileViewModelImpl<T> {
+        ): QSTileViewModel {
             val config = qsTileConfigProvider.getConfig(tileSpec.spec)
-            val component = componentFactory(config)
+            val component =
+                customTileComponentBuilder.qsTileConfigModule(QSTileConfigModule(config)).build()
             return QSTileViewModelImpl(
                 config,
                 component::userActionInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
index 5122e1f..f65fdb5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
@@ -22,9 +22,6 @@
 import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
-import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent
-import com.android.systemui.qs.tiles.impl.custom.di.QSTileConfigModule
-import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
 import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileViewModelAdapter
@@ -41,8 +38,7 @@
     private val adapterFactory: QSTileViewModelAdapter.Factory,
     private val tileMap:
         Map<String, @JvmSuppressWildcards Provider<@JvmSuppressWildcards QSTileViewModel>>,
-    private val customTileComponentBuilder: CustomTileComponent.Builder,
-    private val customTileViewModelFactory: QSTileViewModelFactory.Component<CustomTileDataModel>,
+    private val customTileViewModelFactory: QSTileViewModelFactory.Component,
 ) : QSFactory {
 
     init {
@@ -68,7 +64,5 @@
     }
 
     private fun createCustomTileViewModel(spec: TileSpec.CustomTileSpec): QSTileViewModel =
-        customTileViewModelFactory.create(spec) { config ->
-            customTileComponentBuilder.qsTileConfigModule(QSTileConfigModule(config)).build()
-        }
+        customTileViewModelFactory.create(spec)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 60469c0..b057476 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs.tiles.dialog;
 
+import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING;
+
 import static com.android.settingslib.mobile.MobileMappings.getIconKey;
 import static com.android.settingslib.mobile.MobileMappings.mapIconSets;
 import static com.android.settingslib.wifi.WifiUtils.getHotspotIconResource;
@@ -190,7 +192,7 @@
     private DialogTransitionAnimator mDialogTransitionAnimator;
     private boolean mHasWifiEntries;
     private WifiStateWorker mWifiStateWorker;
-    private boolean mHasActiveSubId;
+    private boolean mHasActiveSubIdOnDds;
 
     @VisibleForTesting
     static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f;
@@ -298,7 +300,7 @@
                 mExecutor);
         // Listen the subscription changes
         mOnSubscriptionsChangedListener = new InternetOnSubscriptionChangedListener();
-        refreshHasActiveSubId();
+        refreshHasActiveSubIdOnDds();
         mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
                 mOnSubscriptionsChangedListener);
         mDefaultDataSubId = getDefaultDataSubscriptionId();
@@ -428,7 +430,7 @@
         }
         boolean isActiveOnNonDds = getActiveAutoSwitchNonDdsSubId() != SubscriptionManager
                 .INVALID_SUBSCRIPTION_ID;
-        if (!hasActiveSubId() || (!isVoiceStateInService(mDefaultDataSubId)
+        if (!hasActiveSubIdOnDds() || (!isVoiceStateInService(mDefaultDataSubId)
                 && !isDataStateInService(mDefaultDataSubId) && !isActiveOnNonDds)) {
             if (DEBUG) {
                 Log.d(TAG, "No carrier or service is out of service.");
@@ -901,23 +903,42 @@
     /**
      * @return whether there is the carrier item in the slice.
      */
-    boolean hasActiveSubId() {
+    boolean hasActiveSubIdOnDds() {
         if (isAirplaneModeEnabled() || mTelephonyManager == null) {
             return false;
         }
 
-        return mHasActiveSubId;
+        return mHasActiveSubIdOnDds;
     }
 
-    private void refreshHasActiveSubId() {
+    private static boolean isEmbeddedSubscriptionVisible(@NonNull SubscriptionInfo subInfo) {
+        if (subInfo.isEmbedded() && subInfo.getProfileClass() == PROFILE_CLASS_PROVISIONING) {
+            return false;
+        }
+        return true;
+    }
+
+    private void refreshHasActiveSubIdOnDds() {
         if (mSubscriptionManager == null) {
-            mHasActiveSubId = false;
+            mHasActiveSubIdOnDds = false;
             Log.e(TAG, "SubscriptionManager is null, set mHasActiveSubId = false");
             return;
         }
+        int dds = getDefaultDataSubscriptionId();
+        if (dds == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            mHasActiveSubIdOnDds = false;
+            Log.d(TAG, "DDS is INVALID_SUBSCRIPTION_ID");
+            return;
+        }
+        SubscriptionInfo ddsSubInfo = mSubscriptionManager.getActiveSubscriptionInfo(dds);
+        if (ddsSubInfo == null) {
+            mHasActiveSubIdOnDds = false;
+            Log.e(TAG, "Can't get DDS subscriptionInfo");
+            return;
+        }
 
-        mHasActiveSubId = mSubscriptionManager.getActiveSubscriptionIdList().length > 0;
-        Log.i(TAG, "mHasActiveSubId:" + mHasActiveSubId);
+        mHasActiveSubIdOnDds = isEmbeddedSubscriptionVisible(ddsSubInfo);
+        Log.i(TAG, "mHasActiveSubId:" + mHasActiveSubIdOnDds);
     }
 
     /**
@@ -1209,7 +1230,7 @@
 
         @Override
         public void onSubscriptionsChanged() {
-            refreshHasActiveSubId();
+            refreshHasActiveSubIdOnDds();
             updateListener();
         }
     }
@@ -1306,6 +1327,7 @@
                     Log.d(TAG, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED");
                 }
                 mConfig = MobileMappings.Config.readConfig(context);
+                refreshHasActiveSubIdOnDds();
                 updateListener();
             } else if (WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION.equals(action)) {
                 updateListener();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index 1a881b6..c971f54 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.qs.tiles.dialog;
 
+import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_WIFI;
 import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA;
 import static com.android.systemui.qs.tiles.dialog.InternetDialogController.MAX_WIFI_ENTRY_COUNT;
 
@@ -57,6 +58,8 @@
 
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
+import com.android.internal.telephony.flags.Flags;
+import com.android.settingslib.satellite.SatelliteDialogUtils;
 import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils;
 import com.android.systemui.Prefs;
 import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan;
@@ -73,6 +76,7 @@
 import dagger.assisted.AssistedInject;
 
 import kotlinx.coroutines.CoroutineScope;
+import kotlinx.coroutines.Job;
 
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -161,6 +165,9 @@
     // Wi-Fi scanning progress bar
     protected boolean mIsProgressBarVisible;
     private SystemUIDialog mDialog;
+    private final CoroutineScope mCoroutineScope;
+    @Nullable
+    private Job mClickJob;
 
     @AssistedFactory
     public interface Factory {
@@ -203,7 +210,7 @@
         mCanConfigWifi = canConfigWifi;
         mCanChangeWifiState = WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context);
         mKeyguard = keyguardStateController;
-
+        mCoroutineScope = coroutineScope;
         mUiEventLogger = uiEventLogger;
         mDialogTransitionAnimator = dialogTransitionAnimator;
         mAdapter = new InternetAdapter(mInternetDialogController, coroutineScope);
@@ -388,11 +395,9 @@
         });
         mConnectedWifListLayout.setOnClickListener(this::onClickConnectedWifi);
         mSeeAllLayout.setOnClickListener(this::onClickSeeMoreButton);
-        mWiFiToggle.setOnCheckedChangeListener(
-                (buttonView, isChecked) -> {
-                    if (mInternetDialogController.isWifiEnabled() == isChecked) return;
-                    mInternetDialogController.setWifiEnabled(isChecked);
-                });
+        mWiFiToggle.setOnClickListener(v -> {
+            handleWifiToggleClicked(mWiFiToggle.isChecked());
+        });
         mDoneButton.setOnClickListener(v -> dialog.dismiss());
         mShareWifiButton.setOnClickListener(v -> {
             if (mInternetDialogController.mayLaunchShareWifiSettings(mConnectedWifiEntry, v)) {
@@ -404,6 +409,32 @@
         });
     }
 
+    private void handleWifiToggleClicked(boolean isChecked) {
+        if (Flags.oemEnabledSatelliteFlag()) {
+            if (mClickJob != null && !mClickJob.isCompleted()) {
+                return;
+            }
+            mClickJob = SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+                    mDialog.getContext(), mCoroutineScope, TYPE_IS_WIFI, isAllowClick -> {
+                        if (isAllowClick) {
+                            setWifiEnable(isChecked);
+                        } else {
+                            mWiFiToggle.setChecked(!isChecked);
+                        }
+                        return null;
+                    });
+            return;
+        }
+        setWifiEnable(isChecked);
+    }
+
+    private void setWifiEnable(boolean isChecked) {
+        if (mInternetDialogController.isWifiEnabled() == isChecked) {
+            return;
+        }
+        mInternetDialogController.setWifiEnabled(isChecked);
+    }
+
     @MainThread
     private void updateEthernet() {
         mEthernetLayout.setVisibility(
@@ -429,7 +460,7 @@
         }
 
         boolean isWifiEnabled = mInternetDialogController.isWifiEnabled();
-        if (!mInternetDialogController.hasActiveSubId()
+        if (!mInternetDialogController.hasActiveSubIdOnDds()
                 && (!isWifiEnabled || !isCarrierNetworkActive)) {
             mMobileNetworkLayout.setVisibility(View.GONE);
             if (mSecondaryMobileNetworkLayout != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt
index d1c8030..bd2f2c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt
@@ -17,15 +17,15 @@
 package com.android.systemui.qs.tiles.impl.location.domain.interactor
 
 import android.os.UserHandle
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow
 import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
 import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
 import com.android.systemui.statusbar.policy.LocationController
+import com.android.systemui.util.kotlin.isLocationEnabledFlow
 import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
 
 /** Observes location state changes providing the [LocationTileModel]. */
 class LocationTileDataInteractor
@@ -38,19 +38,7 @@
         user: UserHandle,
         triggers: Flow<DataUpdateTrigger>
     ): Flow<LocationTileModel> =
-        ConflatedCallbackFlow.conflatedCallbackFlow {
-            val initialValue = locationController.isLocationEnabled
-            trySend(LocationTileModel(initialValue))
-
-            val callback =
-                object : LocationController.LocationChangeCallback {
-                    override fun onLocationSettingsChanged(locationEnabled: Boolean) {
-                        trySend(LocationTileModel(locationEnabled))
-                    }
-                }
-            locationController.addCallback(callback)
-            awaitClose { locationController.removeCallback(callback) }
-        }
+        locationController.isLocationEnabledFlow().map { LocationTileModel(it) }
 
     override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt
new file mode 100644
index 0000000..88bd224
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.qs.tiles.impl.night.domain.interactor
+
+import android.content.Context
+import android.hardware.display.ColorDisplayManager
+import android.os.UserHandle
+import com.android.systemui.accessibility.data.repository.NightDisplayRepository
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.util.time.DateFormatUtil
+import java.time.LocalTime
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/** Observes screen record state changes providing the [NightDisplayTileModel]. */
+class NightDisplayTileDataInteractor
+@Inject
+constructor(
+    @Application private val context: Context,
+    private val dateFormatUtil: DateFormatUtil,
+    private val nightDisplayRepository: NightDisplayRepository,
+) : QSTileDataInteractor<NightDisplayTileModel> {
+
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<NightDisplayTileModel> =
+        nightDisplayRepository.nightDisplayState(user).map {
+            generateModel(
+                it.autoMode,
+                it.isActivated,
+                it.startTime,
+                it.endTime,
+                it.shouldForceAutoMode,
+                it.locationEnabled
+            )
+        }
+
+    /** This checks resources and there fore does not make a binder call. */
+    override fun availability(user: UserHandle): Flow<Boolean> =
+        flowOf(ColorDisplayManager.isNightDisplayAvailable(context))
+
+    private fun generateModel(
+        autoMode: Int,
+        isNightDisplayActivated: Boolean,
+        customStartTime: LocalTime?,
+        customEndTime: LocalTime?,
+        shouldForceAutoMode: Boolean,
+        locationEnabled: Boolean,
+    ): NightDisplayTileModel {
+        if (autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT) {
+            return NightDisplayTileModel.AutoModeTwilight(
+                isNightDisplayActivated,
+                shouldForceAutoMode,
+                locationEnabled,
+            )
+        } else if (autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME) {
+            return NightDisplayTileModel.AutoModeCustom(
+                isNightDisplayActivated,
+                shouldForceAutoMode,
+                customStartTime,
+                customEndTime,
+                dateFormatUtil.is24HourFormat,
+            )
+        } else { // auto mode off
+            return NightDisplayTileModel.AutoModeOff(isNightDisplayActivated, shouldForceAutoMode)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractor.kt
new file mode 100644
index 0000000..5cee8c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractor.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.qs.tiles.impl.night.domain.interactor
+
+import android.content.Intent
+import android.hardware.display.ColorDisplayManager.AUTO_MODE_CUSTOM_TIME
+import android.provider.Settings
+import com.android.systemui.accessibility.data.repository.NightDisplayRepository
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/** Handles night display tile clicks. */
+class NightDisplayTileUserActionInteractor
+@Inject
+constructor(
+    private val nightDisplayRepository: NightDisplayRepository,
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+    private val qsLogger: QSTileLogger,
+) : QSTileUserActionInteractor<NightDisplayTileModel> {
+    override suspend fun handleInput(input: QSTileInput<NightDisplayTileModel>): Unit =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    // Enroll in forced auto mode if eligible.
+                    if (data.isEnrolledInForcedNightDisplayAutoMode) {
+                        nightDisplayRepository.setNightDisplayAutoMode(AUTO_MODE_CUSTOM_TIME, user)
+                        qsLogger.logInfo(spec, "Enrolled in forced night display auto mode")
+                    }
+                    nightDisplayRepository.setNightDisplayActivated(!data.isActivated, user)
+                }
+                is QSTileUserAction.LongClick -> {
+                    qsTileIntentUserActionHandler.handle(
+                        action.expandable,
+                        Intent(Settings.ACTION_NIGHT_DISPLAY_SETTINGS)
+                    )
+                }
+            }
+        }
+
+    companion object {
+        val spec = TileSpec.create(QSAccessibilityModule.NIGHT_DISPLAY_TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/model/NightDisplayTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/model/NightDisplayTileModel.kt
new file mode 100644
index 0000000..6b1bd5b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/model/NightDisplayTileModel.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.qs.tiles.impl.night.domain.model
+
+import java.time.LocalTime
+
+/** Data model for night display tile */
+sealed interface NightDisplayTileModel {
+    val isActivated: Boolean
+    val isEnrolledInForcedNightDisplayAutoMode: Boolean
+    data class AutoModeTwilight(
+        override val isActivated: Boolean,
+        override val isEnrolledInForcedNightDisplayAutoMode: Boolean,
+        val isLocationEnabled: Boolean
+    ) : NightDisplayTileModel
+    data class AutoModeCustom(
+        override val isActivated: Boolean,
+        override val isEnrolledInForcedNightDisplayAutoMode: Boolean,
+        val startTime: LocalTime?,
+        val endTime: LocalTime?,
+        val is24HourFormat: Boolean
+    ) : NightDisplayTileModel
+    data class AutoModeOff(
+        override val isActivated: Boolean,
+        override val isEnrolledInForcedNightDisplayAutoMode: Boolean
+    ) : NightDisplayTileModel
+}
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
new file mode 100644
index 0000000..5c2dcfc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.qs.tiles.impl.night.ui
+
+import android.content.res.Resources
+import android.service.quicksettings.Tile
+import android.text.TextUtils
+import androidx.annotation.StringRes
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import java.time.DateTimeException
+import java.time.LocalTime
+import java.time.format.DateTimeFormatter
+import javax.inject.Inject
+
+/** Maps [NightDisplayTileModel] to [QSTileState]. */
+class NightDisplayTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Resources.Theme,
+    private val logger: QSTileLogger,
+) : QSTileDataToStateMapper<NightDisplayTileModel> {
+    override fun map(config: QSTileConfig, data: NightDisplayTileModel): QSTileState =
+        QSTileState.build(resources, theme, config.uiConfig) {
+            label = resources.getString(R.string.quick_settings_night_display_label)
+            supportedActions =
+                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+            sideViewIcon = QSTileState.SideViewIcon.None
+
+            if (data.isActivated) {
+                activationState = QSTileState.ActivationState.ACTIVE
+                val loadedIcon =
+                    Icon.Loaded(
+                        resources.getDrawable(R.drawable.qs_nightlight_icon_on, theme),
+                        contentDescription = null
+                    )
+                icon = { loadedIcon }
+            } else {
+                activationState = QSTileState.ActivationState.INACTIVE
+                val loadedIcon =
+                    Icon.Loaded(
+                        resources.getDrawable(R.drawable.qs_nightlight_icon_off, theme),
+                        contentDescription = null
+                    )
+                icon = { loadedIcon }
+            }
+
+            secondaryLabel = getSecondaryLabel(data, resources)
+
+            contentDescription =
+                if (TextUtils.isEmpty(secondaryLabel)) label
+                else TextUtils.concat(label, ", ", secondaryLabel)
+        }
+
+    private fun getSecondaryLabel(
+        data: NightDisplayTileModel,
+        resources: Resources
+    ): CharSequence? {
+        when (data) {
+            is NightDisplayTileModel.AutoModeTwilight -> {
+                if (!data.isLocationEnabled) {
+                    return null
+                } else {
+                    return resources.getString(
+                        if (data.isActivated)
+                            R.string.quick_settings_night_secondary_label_until_sunrise
+                        else R.string.quick_settings_night_secondary_label_on_at_sunset
+                    )
+                }
+            }
+            is NightDisplayTileModel.AutoModeOff -> {
+                val subtitleArray = resources.getStringArray(R.array.tile_states_night)
+                return subtitleArray[
+                    if (data.isActivated) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE]
+            }
+            is NightDisplayTileModel.AutoModeCustom -> {
+                // User-specified time, approximated to the nearest hour.
+                @StringRes val toggleTimeStringRes: Int
+                val toggleTime: LocalTime
+                if (data.isActivated) {
+                    toggleTime = data.endTime ?: return null
+                    toggleTimeStringRes = R.string.quick_settings_secondary_label_until
+                } else {
+                    toggleTime = data.startTime ?: return null
+                    toggleTimeStringRes = R.string.quick_settings_night_secondary_label_on_at
+                }
+
+                try {
+                    val formatter = if (data.is24HourFormat) formatter24Hour else formatter12Hour
+                    val formatArg = formatter.format(toggleTime)
+                    return resources.getString(toggleTimeStringRes, formatArg)
+                } catch (exception: DateTimeException) {
+                    logger.logWarning(spec, exception.message.toString())
+                    return null
+                }
+            }
+        }
+    }
+
+    private companion object {
+        val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a")
+        val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
+        val spec = TileSpec.create(QSAccessibilityModule.NIGHT_DISPLAY_TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileDataInteractor.kt
new file mode 100644
index 0000000..8c0fd2c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileDataInteractor.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.qs.tiles.impl.onehanded.domain
+
+import android.os.UserHandle
+import com.android.systemui.accessibility.data.repository.OneHandedModeRepository
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
+import com.android.wm.shell.onehanded.OneHanded
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/** Observes one handed mode state changes providing the [OneHandedModeTileModel]. */
+class OneHandedModeTileDataInteractor
+@Inject
+constructor(
+    private val oneHandedModeRepository: OneHandedModeRepository,
+) : QSTileDataInteractor<OneHandedModeTileModel> {
+
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<OneHandedModeTileModel> {
+        return oneHandedModeRepository.isEnabled(user).map { OneHandedModeTileModel(it) }
+    }
+    override fun availability(user: UserHandle): Flow<Boolean> =
+        flowOf(OneHanded.sIsSupportOneHandedMode)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileUserActionInteractor.kt
new file mode 100644
index 0000000..5cb0e18
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileUserActionInteractor.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.qs.tiles.impl.onehanded.domain
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.accessibility.data.repository.OneHandedModeRepository
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/** Handles one handed mode tile clicks. */
+class OneHandedModeTileUserActionInteractor
+@Inject
+constructor(
+    private val oneHandedModeRepository: OneHandedModeRepository,
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+) : QSTileUserActionInteractor<OneHandedModeTileModel> {
+
+    override suspend fun handleInput(input: QSTileInput<OneHandedModeTileModel>): Unit =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    oneHandedModeRepository.setIsEnabled(
+                        !data.isEnabled,
+                        user,
+                    )
+                }
+                is QSTileUserAction.LongClick -> {
+                    qsTileIntentUserActionHandler.handle(
+                        action.expandable,
+                        Intent(Settings.ACTION_ONE_HANDED_SETTINGS)
+                    )
+                }
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/model/OneHandedModeTileModel.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/model/OneHandedModeTileModel.kt
index bf22563..7cebdfe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/model/OneHandedModeTileModel.kt
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.tiles.impl.onehanded.domain.model
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
-
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+/**
+ * One handed mode tile model.
+ *
+ * @param isEnabled is true when one handed mode is enabled;
+ */
+@JvmInline value class OneHandedModeTileModel(val isEnabled: Boolean)
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
new file mode 100644
index 0000000..9166ed8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.qs.tiles.impl.onehanded.ui
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [OneHandedModeTileModel] to [QSTileState]. */
+class OneHandedModeTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<OneHandedModeTileModel> {
+
+    override fun map(config: QSTileConfig, data: OneHandedModeTileModel): QSTileState =
+        QSTileState.build(resources, theme, config.uiConfig) {
+            val subtitleArray = resources.getStringArray(R.array.tile_states_onehanded)
+            label = resources.getString(R.string.quick_settings_onehanded_label)
+            icon = {
+                Icon.Loaded(
+                    resources.getDrawable(
+                        com.android.internal.R.drawable.ic_qs_one_handed_mode,
+                        theme
+                    ),
+                    null
+                )
+            }
+            if (data.isEnabled) {
+                activationState = QSTileState.ActivationState.ACTIVE
+                secondaryLabel = subtitleArray[2]
+            } else {
+                activationState = QSTileState.ActivationState.INACTIVE
+                secondaryLabel = subtitleArray[1]
+            }
+            sideViewIcon = QSTileState.SideViewIcon.None
+            contentDescription = label
+            supportedActions =
+                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt
new file mode 100644
index 0000000..1e8ce58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.qs.tiles.impl.qr.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+/** Observes one qr scanner state changes providing the [QRCodeScannerTileModel]. */
+class QRCodeScannerTileDataInteractor
+@Inject
+constructor(
+    @Background private val bgCoroutineContext: CoroutineContext,
+    @Application private val scope: CoroutineScope,
+    private val qrController: QRCodeScannerController,
+) : QSTileDataInteractor<QRCodeScannerTileModel> {
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<QRCodeScannerTileModel> =
+        conflatedCallbackFlow {
+                qrController.registerQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE)
+                val callback =
+                    object : QRCodeScannerController.Callback {
+                        override fun onQRCodeScannerActivityChanged() {
+                            trySend(generateModel())
+                        }
+                    }
+                qrController.addCallback(callback)
+                awaitClose {
+                    qrController.removeCallback(callback)
+                    qrController.unregisterQRCodeScannerChangeObservers(
+                        DEFAULT_QR_CODE_SCANNER_CHANGE
+                    )
+                }
+            }
+            .onStart { emit(generateModel()) }
+            .flowOn(bgCoroutineContext)
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                QRCodeScannerTileModel.TemporarilyUnavailable
+            )
+
+    override fun availability(user: UserHandle): Flow<Boolean> =
+        flowOf(qrController.isCameraAvailable)
+
+    private fun generateModel(): QRCodeScannerTileModel {
+        val intent = qrController.intent
+
+        return if (qrController.isAbleToLaunchScannerActivity && intent != null)
+            QRCodeScannerTileModel.Available(intent)
+        else QRCodeScannerTileModel.TemporarilyUnavailable
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt
new file mode 100644
index 0000000..7c0c41e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.qs.tiles.impl.qr.domain.interactor
+
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/** Handles qr tile clicks. */
+class QRCodeScannerTileUserActionInteractor
+@Inject
+constructor(
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+) : QSTileUserActionInteractor<QRCodeScannerTileModel> {
+
+    override suspend fun handleInput(input: QSTileInput<QRCodeScannerTileModel>): Unit =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    when (data) {
+                        is QRCodeScannerTileModel.Available ->
+                            qsTileIntentUserActionHandler.handle(
+                                action.expandable,
+                                data.intent,
+                                true
+                            )
+                        is QRCodeScannerTileModel.TemporarilyUnavailable -> {} // no-op
+                    }
+                }
+                is QSTileUserAction.LongClick -> {} // no-op
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt
index bf22563..22c9b66 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.tiles.impl.qr.domain.model
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import android.content.Intent
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
+/** qr scanner tile model. */
+sealed interface QRCodeScannerTileModel {
+    data class Available(val intent: Intent) : QRCodeScannerTileModel
+    data object TemporarilyUnavailable : QRCodeScannerTileModel
 }
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
new file mode 100644
index 0000000..45a7717
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.qs.tiles.impl.qr.ui
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [QRCodeScannerTileModel] to [QSTileState]. */
+class QRCodeScannerTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<QRCodeScannerTileModel> {
+
+    override fun map(config: QSTileConfig, data: QRCodeScannerTileModel): QSTileState =
+        QSTileState.build(resources, theme, config.uiConfig) {
+            label = resources.getString(R.string.qr_code_scanner_title)
+            contentDescription = label
+            icon = {
+                Icon.Loaded(resources.getDrawable(R.drawable.ic_qr_code_scanner, theme), null)
+            }
+            sideViewIcon = QSTileState.SideViewIcon.Chevron
+            supportedActions = setOf(QSTileState.UserAction.CLICK)
+
+            when (data) {
+                is QRCodeScannerTileModel.Available -> {
+                    activationState = QSTileState.ActivationState.INACTIVE
+                    secondaryLabel = null
+                }
+                is QRCodeScannerTileModel.TemporarilyUnavailable -> {
+                    activationState = QSTileState.ActivationState.UNAVAILABLE
+                    secondaryLabel =
+                        resources.getString(R.string.qr_code_scanner_updating_secondary_label)
+                }
+            }
+        }
+}
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 b88c1e5..5346b23 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
@@ -201,6 +201,7 @@
         qsTileViewModel.currentState?.let { mapState(context, it, qsTileViewModel.config) }
 
     override fun getInstanceId(): InstanceId = qsTileViewModel.config.instanceId
+
     override fun getTileLabel(): CharSequence =
         with(qsTileViewModel.config.uiConfig) {
             when (this) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
index d6325c0..a04fa38 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel
 import javax.inject.Inject
 
@@ -27,4 +28,5 @@
 constructor(
     val brightnessSliderViewModel: BrightnessSliderViewModel,
     val tileGridViewModel: TileGridViewModel,
+    val editModeViewModel: EditModeViewModel,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index 17698f9d..6cf2e52 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -29,7 +29,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
 import com.android.systemui.qs.FooterActionsController
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
@@ -63,7 +63,7 @@
     private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
     private val footerActionsController: FooterActionsController,
     sceneBackInteractor: SceneBackInteractor,
-    val mediaDataManager: MediaDataManager,
+    val mediaCarouselInteractor: MediaCarouselInteractor,
 ) {
     private val backScene: StateFlow<SceneKey> =
         sceneBackInteractor.backScene
@@ -101,6 +101,8 @@
                     ),
             )
 
+    val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation
+
     private fun destinationScenes(
         isUnlocked: Boolean,
         canSwipeToDismiss: Boolean?,
@@ -143,9 +145,4 @@
         }
         return footerActionsViewModelFactory.create(lifecycleOwner)
     }
-
-    fun isMediaVisible(): Boolean {
-        // TODO(b/328207006): use new pipeline to handle updates while visible
-        return mediaDataManager.hasAnyMediaOrRecommendation()
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 0673dcd..0327ec7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -37,7 +37,6 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_TRANSITION;
 
@@ -109,13 +108,14 @@
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
 import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
 import com.android.systemui.statusbar.policy.CallbackController;
 import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder;
-import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellInterface;
 
 import dagger.Lazy;
@@ -415,7 +415,13 @@
         @Override
         public void toggleNotificationPanel() {
             verifyCallerAndClearCallingIdentityPostMain("toggleNotificationPanel", () ->
-                    mCommandQueue.togglePanel());
+                    mCommandQueue.toggleNotificationsPanel());
+        }
+
+        @Override
+        public void toggleQuickSettingsPanel() {
+            verifyCallerAndClearCallingIdentityPostMain("toggleQuickSettingsPanel", () ->
+                    mCommandQueue.toggleQuickSettingsPanel());
         }
 
         private boolean verifyCaller(String reason) {
@@ -701,15 +707,15 @@
             // Listen for tracing state changes
             @Override
             public void onTracingStateChanged(boolean enabled) {
-                mSysUiState.setFlag(SYSUI_STATE_TRACING_ENABLED, enabled)
-                        .commitUpdate(mContext.getDisplayId());
+                // TODO(b/286509643) Cleanup callers of this; Unused downstream
             }
 
             @Override
             public void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) {
                 if (mOverviewProxy != null) {
                     try {
-                        if (DesktopModeStatus.isEnabled() && (sysUiState.getFlags()
+                        if (DesktopModeStatus.canEnterDesktopMode(mContext)
+                                && (sysUiState.getFlags()
                                 & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0) {
                             return;
                         }
@@ -766,7 +772,7 @@
         }
     }
 
-    private void notifySystemUiStateFlags(int flags) {
+    private void notifySystemUiStateFlags(@SystemUiStateFlags long flags) {
         if (SysUiState.DEBUG) {
             Log.d(TAG_OPS, "Notifying sysui state change to overview service: proxy="
                     + mOverviewProxy + " flags=" + flags);
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 4e290e6..6694878 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -20,7 +20,6 @@
 import android.app.NotificationManager
 import android.content.Context
 import android.content.Intent
-import android.content.pm.LauncherApps
 import android.content.res.Resources
 import android.net.Uri
 import android.os.Handler
@@ -63,7 +62,6 @@
     private val panelInteractor: PanelInteractor,
     private val issueRecordingState: IssueRecordingState,
     private val iActivityManager: IActivityManager,
-    private val launcherApps: LauncherApps,
 ) :
     RecordingService(
         controller,
@@ -85,7 +83,7 @@
         when (intent?.action) {
             ACTION_START -> {
                 TraceUtils.traceStart(
-                    contentResolver,
+                    this,
                     DEFAULT_TRACE_TAGS,
                     DEFAULT_BUFFER_SIZE,
                     DEFAULT_IS_INCLUDING_WINSCOPE,
@@ -104,11 +102,7 @@
             }
             ACTION_STOP,
             ACTION_STOP_NOTIF -> {
-                // ViewCapture needs to save it's data before it is disabled, or else the data will
-                // be lost. This is expected to change in the near future, and when that happens
-                // this line should be removed.
-                launcherApps.saveViewCaptureData()
-                TraceUtils.traceStop(contentResolver)
+                TraceUtils.traceStop(this)
                 issueRecordingState.isRecording = false
             }
             ACTION_SHARE -> {
@@ -142,7 +136,7 @@
 
     private fun shareRecording(screenRecording: Uri?) {
         val traces =
-            TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).getOrElse {
+            TraceUtils.traceDump(this, TRACE_FILE_NAME).getOrElse {
                 Log.v(
                     TAG,
                     "Traces were not present. This can happen if users double" +
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index 28569d8..8169dec 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.scene
 
 import com.android.systemui.CoreStartable
+import com.android.systemui.notifications.ui.composable.NotificationsShadeSessionModule
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
 import com.android.systemui.scene.domain.startable.SceneContainerStartable
 import com.android.systemui.scene.shared.model.SceneContainerConfig
@@ -35,6 +36,7 @@
             EmptySceneModule::class,
             GoneSceneModule::class,
             NotificationsShadeSceneModule::class,
+            NotificationsShadeSessionModule::class,
             QuickSettingsSceneModule::class,
             ShadeSceneModule::class,
         ],
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index dbe0342..9bd2694 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlagsModule
+import com.android.systemui.notifications.ui.composable.NotificationsShadeSessionModule
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
 import com.android.systemui.scene.domain.startable.SceneContainerStartable
 import com.android.systemui.scene.shared.model.SceneContainerConfig
@@ -43,6 +44,7 @@
             ShadeSceneModule::class,
             QuickSettingsShadeSceneModule::class,
             NotificationsShadeSceneModule::class,
+            NotificationsShadeSessionModule::class,
         ],
 )
 interface SceneContainerFrameworkModule {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneStack.kt b/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneStack.kt
new file mode 100644
index 0000000..d3e529c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneStack.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.scene.data.model
+
+import com.android.compose.animation.scene.SceneKey
+
+/** An immutable stack of [SceneKey]s backed by a singly-linked list. */
+sealed interface SceneStack
+
+private data object EmptyStack : SceneStack
+
+private data class StackedNodes(val head: SceneKey, val tail: SceneStack) : SceneStack
+
+/** Returns the scene at the head of the stack, or `null` if empty. O(1) */
+fun SceneStack.peek(): SceneKey? =
+    when (this) {
+        EmptyStack -> null
+        is StackedNodes -> head
+    }
+
+/** Returns a stack with the head removed, or `null` if empty. O(1) */
+fun SceneStack.pop(): SceneStack? =
+    when (this) {
+        EmptyStack -> null
+        is StackedNodes -> tail
+    }
+
+/** Returns a stack with [sceneKey] as the head on top of [this]. O(1) */
+fun SceneStack.push(sceneKey: SceneKey): SceneStack = StackedNodes(sceneKey, this)
+
+/** Returns an iterable that produces all elements in the stack, from head to tail. */
+fun SceneStack.asIterable(): Iterable<SceneKey> = Iterable {
+    iterator {
+        when (this@asIterable) {
+            EmptyStack -> {}
+            is StackedNodes -> {
+                yield(head)
+                yieldAll(tail.asIterable())
+            }
+        }
+    }
+}
+
+/**
+ * Returns a new [SceneStack] containing the given [scenes], ordered such that the first argument is
+ * the head returned from [peek], then the second, and so forth.
+ */
+fun sceneStackOf(vararg scenes: SceneKey): SceneStack {
+    var result: SceneStack = EmptyStack
+    for (sceneKey in scenes.reversed()) {
+        result = result.push(sceneKey)
+    }
+    return result
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index 5748ad4..3e2c630 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -21,6 +21,7 @@
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.TransitionKey
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneDataSource
@@ -36,6 +37,7 @@
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.stateIn
 
+@SysUISingleton
 /** Source of truth for scene framework application state. */
 class SceneContainerRepository
 @Inject
@@ -87,6 +89,14 @@
         )
     }
 
+    fun snapToScene(
+        toScene: SceneKey,
+    ) {
+        dataSource.snapToScene(
+            toScene = toScene,
+        )
+    }
+
     /** Sets whether the container is visible. */
     fun setVisible(isVisible: Boolean) {
         _isVisible.value = isVisible
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt
index f66d08f..c176cca 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt
@@ -18,13 +18,21 @@
 
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.data.model.SceneStack
+import com.android.systemui.scene.data.model.asIterable
+import com.android.systemui.scene.data.model.peek
+import com.android.systemui.scene.data.model.pop
+import com.android.systemui.scene.data.model.push
+import com.android.systemui.scene.data.model.sceneStackOf
 import com.android.systemui.scene.shared.logger.SceneLogger
 import com.android.systemui.scene.shared.model.SceneContainerConfig
-import java.util.Stack
 import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.update
 
 @SysUISingleton
 class SceneBackInteractor
@@ -33,7 +41,9 @@
     private val logger: SceneLogger,
     private val sceneContainerConfig: SceneContainerConfig,
 ) {
-    private val _backScene = MutableStateFlow<SceneKey?>(null)
+    private val _backStack = MutableStateFlow(sceneStackOf())
+    val backStack: StateFlow<SceneStack> = _backStack.asStateFlow()
+
     /**
      * The scene to navigate to when the user triggers back navigation.
      *
@@ -44,30 +54,30 @@
      * illegal state to have scene implementation map to itself in its destination scene flow. Thus,
      * scene implementations might wish to filter their own scene key out before using this.
      */
-    val backScene: StateFlow<SceneKey?> = _backScene.asStateFlow()
-
-    private val backStack = Stack<SceneKey>()
+    val backScene: Flow<SceneKey?> = backStack.map { it.peek() }
 
     fun onSceneChange(from: SceneKey, to: SceneKey) {
         check(from != to) { "from == to, from=${from.debugName}, to=${to.debugName}" }
         when (stackOperation(from, to)) {
             Clear -> {
-                backStack.clear()
+                _backStack.value = sceneStackOf()
             }
             Push -> {
-                backStack.push(from)
+                _backStack.update { s -> s.push(from) }
             }
             Pop -> {
-                check(backStack.isNotEmpty()) { "Cannot pop ${from.debugName} when stack is empty" }
-                val popped = backStack.pop()
-                check(to == popped) {
-                    "Expected to pop ${to.debugName} but instead popped ${popped.debugName}"
+                _backStack.update { s ->
+                    checkNotNull(s.pop()) { "Cannot pop ${from.debugName} when stack is empty" }
+                        .also {
+                            val popped = s.peek()
+                            check(popped == to) {
+                                "Expected to pop ${to.debugName} but instead popped ${popped?.debugName}"
+                            }
+                        }
                 }
             }
         }
-
-        logger.logSceneBackStack(backStack)
-        _backScene.value = peek()
+        logger.logSceneBackStack(backStack.value.asIterable())
     }
 
     private fun stackOperation(from: SceneKey, to: SceneKey): StackOperation {
@@ -92,14 +102,6 @@
         }
     }
 
-    private fun peek(): SceneKey? {
-        return if (backStack.isNotEmpty()) {
-            backStack.peek()
-        } else {
-            null
-        }
-    }
-
     private sealed interface StackOperation
     private data object Clear : StackOperation
     private data object Push : StackOperation
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 93cef61..0d0f6e0 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -55,6 +55,18 @@
     private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
 ) {
 
+    interface OnSceneAboutToChangeListener {
+
+        /**
+         * Notifies that the scene is about to change to [toScene].
+         *
+         * The implementation can choose to consume the [sceneState] to prepare the incoming scene.
+         */
+        fun onSceneAboutToChange(toScene: SceneKey, sceneState: Any?)
+    }
+
+    private val onSceneAboutToChangeListener = mutableSetOf<OnSceneAboutToChangeListener>()
+
     /**
      * The current scene.
      *
@@ -149,6 +161,10 @@
         return repository.allSceneKeys()
     }
 
+    fun registerSceneStateProcessor(processor: OnSceneAboutToChangeListener) {
+        onSceneAboutToChangeListener.add(processor)
+    }
+
     /**
      * Requests a scene change to the given scene.
      *
@@ -161,20 +177,16 @@
         toScene: SceneKey,
         loggingReason: String,
         transitionKey: TransitionKey? = null,
+        sceneState: Any? = null,
     ) {
-        if (!repository.allSceneKeys().contains(toScene)) {
-            return
-        }
-
-        check(
-            toScene != Scenes.Gone || deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked
-        ) {
-            "Cannot change to the Gone scene while the device is locked. Logging reason for scene" +
-                " change was: $loggingReason"
-        }
-
         val currentSceneKey = currentScene.value
-        if (currentSceneKey == toScene) {
+        if (
+            !validateSceneChange(
+                from = currentSceneKey,
+                to = toScene,
+                loggingReason = loggingReason,
+            )
+        ) {
             return
         }
 
@@ -182,12 +194,45 @@
             from = currentSceneKey,
             to = toScene,
             reason = loggingReason,
+            isInstant = false,
         )
 
+        onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(toScene, sceneState) }
         repository.changeScene(toScene, transitionKey)
     }
 
     /**
+     * Requests a scene change to the given scene.
+     *
+     * The change is instantaneous and not animated; it will be observable in the next frame and
+     * there will be no transition animation.
+     */
+    fun snapToScene(
+        toScene: SceneKey,
+        loggingReason: String,
+    ) {
+        val currentSceneKey = currentScene.value
+        if (
+            !validateSceneChange(
+                from = currentSceneKey,
+                to = toScene,
+                loggingReason = loggingReason,
+            )
+        ) {
+            return
+        }
+
+        logger.logSceneChangeRequested(
+            from = currentSceneKey,
+            to = toScene,
+            reason = loggingReason,
+            isInstant = true,
+        )
+
+        repository.snapToScene(toScene)
+    }
+
+    /**
      * Sets the visibility of the container.
      *
      * Please do not call this from outside of the scene framework. If you are trying to force the
@@ -249,4 +294,32 @@
     ): Boolean {
         return raw || isRemoteUserInteractionOngoing
     }
+
+    /**
+     * Validates that the given scene change is allowed.
+     *
+     * Will throw a runtime exception for illegal states (for example, attempting to change to a
+     * scene that's not part of the current scene framework configuration).
+     *
+     * @param from The current scene being transitioned away from
+     * @param to The desired destination scene to transition to
+     * @param loggingReason The reason why the transition is requested, for logging purposes
+     * @return `true` if the scene change is valid; `false` if it shouldn't happen
+     */
+    private fun validateSceneChange(
+        from: SceneKey,
+        to: SceneKey,
+        loggingReason: String,
+    ): Boolean {
+        if (!repository.allSceneKeys().contains(to)) {
+            return false
+        }
+
+        check(to != Scenes.Gone || deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked) {
+            "Cannot change to the Gone scene while the device is locked. Logging reason for scene" +
+                " change was: $loggingReason"
+        }
+
+        return from != to
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 9e57964..3ce12dd 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -45,9 +45,11 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.FalsingManager.FalsingBeliefListener
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.data.model.asIterable
 import com.android.systemui.scene.domain.interactor.SceneBackInteractor
 import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.session.shared.SessionStorage
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.logger.SceneLogger
 import com.android.systemui.scene.shared.model.Scenes
@@ -116,6 +118,7 @@
     private val shadeInteractor: ShadeInteractor,
     private val uiEventLogger: UiEventLogger,
     private val sceneBackInteractor: SceneBackInteractor,
+    private val shadeSessionStorage: SessionStorage,
 ) : CoreStartable {
     private val centralSurfaces: CentralSurfaces?
         get() = centralSurfacesOptLazy.get().getOrNull()
@@ -132,6 +135,7 @@
             handleBouncerOverscroll()
             hydrateWindowController()
             hydrateBackStack()
+            resetShadeSessions()
         } else {
             sceneLogger.logFrameworkEnabled(
                 isEnabled = false,
@@ -150,6 +154,20 @@
             }
         }
 
+    private fun resetShadeSessions() {
+        applicationScope.launch {
+            sceneBackInteractor.backStack
+                // We are in a session if either Shade or QuickSettings is on the back stack
+                .map { backStack ->
+                    backStack.asIterable().any { it == Scenes.Shade || it == Scenes.QuickSettings }
+                }
+                .distinctUntilChanged()
+                // Once a session has ended, clear the session storage.
+                .filter { inSession -> !inSession }
+                .collect { shadeSessionStorage.clear() }
+        }
+    }
+
     /** Updates the visibility of the scene container. */
     private fun hydrateVisibility() {
         applicationScope.launch {
@@ -216,7 +234,7 @@
             bouncerInteractor.onImeHiddenByUser.collectLatest {
                 if (sceneInteractor.currentScene.value == Scenes.Bouncer) {
                     sceneInteractor.changeScene(
-                        toScene = Scenes.Lockscreen,
+                        toScene = Scenes.Lockscreen, // TODO(b/336581871): add sceneState?
                         loggingReason = "IME hidden",
                     )
                 }
@@ -234,6 +252,7 @@
                     when {
                         isAnySimLocked -> {
                             switchToScene(
+                                // TODO(b/336581871): add sceneState?
                                 targetSceneKey = Scenes.Bouncer,
                                 loggingReason = "Need to authenticate locked SIM card."
                             )
@@ -241,6 +260,7 @@
                         unlockStatus.isUnlocked &&
                             deviceEntryInteractor.canSwipeToEnter.value == false -> {
                             switchToScene(
+                                // TODO(b/336581871): add sceneState?
                                 targetSceneKey = Scenes.Gone,
                                 loggingReason =
                                     "All SIM cards unlocked and device already unlocked and " +
@@ -249,6 +269,7 @@
                         }
                         else -> {
                             switchToScene(
+                                // TODO(b/336581871): add sceneState?
                                 targetSceneKey = Scenes.Lockscreen,
                                 loggingReason =
                                     "All SIM cards unlocked and device still locked" +
@@ -347,6 +368,7 @@
             powerInteractor.isAsleep.collect { isAsleep ->
                 if (isAsleep) {
                     switchToScene(
+                        // TODO(b/336581871): add sceneState?
                         targetSceneKey = Scenes.Lockscreen,
                         loggingReason = "device is starting to sleep",
                     )
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
index 5ebdd86..9d6720b 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
@@ -20,7 +20,6 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.SceneFrameworkLog
-import java.util.Stack
 import javax.inject.Inject
 
 class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: LogBuffer) {
@@ -47,6 +46,7 @@
         from: SceneKey,
         to: SceneKey,
         reason: String,
+        isInstant: Boolean,
     ) {
         logBuffer.log(
             tag = TAG,
@@ -55,8 +55,17 @@
                 str1 = from.toString()
                 str2 = to.toString()
                 str3 = reason
+                bool1 = isInstant
             },
-            messagePrinter = { "Scene change requested: $str1 → $str2, reason: $str3" },
+            messagePrinter = {
+                buildString {
+                    append("Scene change requested: $str1 → $str2")
+                    if (isInstant) {
+                        append(" (instant)")
+                    }
+                    append(", reason: $str3")
+                }
+            },
         )
     }
 
@@ -116,7 +125,7 @@
         )
     }
 
-    fun logSceneBackStack(backStack: Stack<SceneKey>) {
+    fun logSceneBackStack(backStack: Iterable<SceneKey>) {
         logBuffer.log(
             tag = TAG,
             level = LogLevel.INFO,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt
index 0e078d5..034da25 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt
@@ -40,4 +40,11 @@
         toScene: SceneKey,
         transitionKey: TransitionKey? = null,
     )
+
+    /**
+     * Asks for an instant scene switch to [toScene], without an animated transition of any kind.
+     */
+    fun snapToScene(
+        toScene: SceneKey,
+    )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
index 2fbcba9..43c3635 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
@@ -56,6 +56,12 @@
         )
     }
 
+    override fun snapToScene(toScene: SceneKey) {
+        delegateMutable.value.snapToScene(
+            toScene = toScene,
+        )
+    }
+
     /**
      * Binds the current, dependency injection provided [SceneDataSource] to the given object.
      *
@@ -77,5 +83,7 @@
             MutableStateFlow(initialSceneKey).asStateFlow()
 
         override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) = Unit
+
+        override fun snapToScene(toScene: SceneKey) = Unit
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt
index b91dd04..ef393e4 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt
@@ -24,6 +24,8 @@
  * These are the subset of transitions that can be referenced by key when asking for a scene change.
  */
 object TransitionKeys {
+    /** Reference to the gone/lockscreen to shade transition with split shade enabled. */
+    val ToSplitShade = TransitionKey("GoneToSplitShade")
 
     /** Reference to a scene transition that can collapse the shade scene instantly. */
     val CollapseShadeInstantly = TransitionKey("CollapseShadeInstantly")
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index 259a8bf..b971781 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -56,9 +56,8 @@
     }
 
     // TODO(b/298525212): remove once Compose exposes window inset bounds.
-    override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? {
-        val insets = super.onApplyWindowInsets(windowInsets)
-        this.windowInsets.value = insets
-        return insets
+    override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets {
+        this.windowInsets.value = windowInsets
+        return windowInsets
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index 78704e1..c20d577 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -198,7 +198,7 @@
     private fun getDisplayWidth(context: Context): Dp {
         val point = Point()
         checkNotNull(context.display).getRealSize(point)
-        return point.x.dp
+        return point.x.toDp(context)
     }
 
     // TODO(b/298525212): remove once Compose exposes window inset bounds.
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
index b0af7f9..99118bc 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
 import javax.inject.Inject
@@ -70,10 +71,11 @@
                     )] = UserActionResult(Scenes.QuickSettingsShade)
             }
 
+            val downSceneKey =
+                if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade
+            val downTransitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
             this[Swipe(direction = SwipeDirection.Down)] =
-                UserActionResult(
-                    if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade
-                )
+                UserActionResult(downSceneKey, downTransitionKey)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
index caa67df..1868b4a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
@@ -25,7 +25,6 @@
 import android.os.UserHandle
 import android.util.Log
 import android.util.Pair
-import android.view.View
 import android.view.Window
 import com.android.app.tracing.coroutines.launch
 import com.android.internal.app.ChooserActivity
@@ -41,8 +40,8 @@
     private val intentExecutor: ActionIntentExecutor,
     @Application private val applicationScope: CoroutineScope,
     @Assisted val window: Window,
-    @Assisted val transitionView: View,
-    @Assisted val onDismiss: (() -> Unit)
+    @Assisted val viewProxy: ScreenshotViewProxy,
+    @Assisted val finishDismiss: () -> Unit,
 ) {
 
     var isPendingSharedTransition = false
@@ -50,6 +49,7 @@
 
     fun startSharedTransition(intent: Intent, user: UserHandle, overrideTransition: Boolean) {
         isPendingSharedTransition = true
+        viewProxy.fadeForSharedTransition()
         val windowTransition = createWindowTransition()
         applicationScope.launch("$TAG#launchIntentAsync") {
             intentExecutor.launchIntent(
@@ -70,7 +70,7 @@
                 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
             )
             pendingIntent.send(options.toBundle())
-            onDismiss.invoke()
+            viewProxy.requestDismissal(null)
         } catch (e: PendingIntent.CanceledException) {
             Log.e(TAG, "Intent cancelled", e)
         }
@@ -89,7 +89,7 @@
 
                 override fun hideSharedElements() {
                     isPendingSharedTransition = false
-                    onDismiss.invoke()
+                    finishDismiss.invoke()
                 }
 
                 override fun onFinish() {}
@@ -98,13 +98,20 @@
             window,
             callbacks,
             null,
-            Pair.create(transitionView, ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)
+            Pair.create(
+                viewProxy.screenshotPreview,
+                ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME
+            )
         )
     }
 
     @AssistedFactory
     interface Factory {
-        fun create(window: Window, transitionView: View, onDismiss: (() -> Unit)): ActionExecutor
+        fun create(
+            window: Window,
+            viewProxy: ScreenshotViewProxy,
+            finishDismiss: (() -> Unit)
+        ): ActionExecutor
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
index a0cef52..15638d3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
@@ -97,6 +97,7 @@
             .putExtra(LongScreenshotActivity.EXTRA_SCREENSHOT_USER_HANDLE, owner)
             .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
             .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+            .addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
     }
 
     private const val EXTRA_EDIT_SOURCE = "edit_source"
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index 4eca51d..4ab0918 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -33,10 +33,11 @@
 import android.view.WindowManagerGlobal
 import com.android.app.tracing.coroutines.launch
 import com.android.internal.infra.ServiceConnector
-import com.android.systemui.Flags.screenshotActionDismissSystemWindows
+import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.screenshot.proxy.SystemUiProxy
 import com.android.systemui.settings.DisplayTracker
 import com.android.systemui.shared.system.ActivityManagerWrapper
 import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -54,8 +55,8 @@
     private val activityManagerWrapper: ActivityManagerWrapper,
     @Application private val applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
+    private val systemUiProxy: SystemUiProxy,
     private val displayTracker: DisplayTracker,
-    private val keyguardController: ScreenshotKeyguardController,
 ) {
     /**
      * Execute the given intent with startActivity while performing operations for screenshot action
@@ -83,14 +84,12 @@
         options: ActivityOptions?,
         transitionCoordinator: ExitTransitionCoordinator?,
     ) {
-        if (screenshotActionDismissSystemWindows()) {
-            keyguardController.dismiss()
+        if (Flags.fixScreenshotActionDismissSystemWindows()) {
             activityManagerWrapper.closeSystemWindows(
                 CentralSurfaces.SYSTEM_DIALOG_REASON_SCREENSHOT
             )
-        } else {
-            dismissKeyguard()
         }
+        systemUiProxy.dismissKeyguard()
         transitionCoordinator?.startExit()
 
         if (user == myUserHandle()) {
@@ -110,27 +109,6 @@
         }
     }
 
-    private val proxyConnector: ServiceConnector<IScreenshotProxy> =
-        ServiceConnector.Impl(
-            context,
-            Intent(context, ScreenshotProxyService::class.java),
-            Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
-            context.userId,
-            IScreenshotProxy.Stub::asInterface,
-        )
-
-    private suspend fun dismissKeyguard() {
-        val completion = CompletableDeferred<Unit>()
-        val onDoneBinder =
-            object : IOnDoneCallback.Stub() {
-                override fun onDone(success: Boolean) {
-                    completion.complete(Unit)
-                }
-            }
-        proxyConnector.post { it.dismissKeyguard(onDoneBinder) }
-        completion.await()
-    }
-
     private fun getCrossProfileConnector(user: UserHandle): ServiceConnector<ICrossProfileService> =
         ServiceConnector.Impl<ICrossProfileService>(
             context,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
index 4cf18fb..3d024a6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
@@ -157,6 +157,8 @@
 
     override fun restoreNonScrollingUi() = view.restoreNonScrollingUi()
 
+    override fun fadeForSharedTransition() {} // unused
+
     override fun stopInputListening() = view.stopInputListening()
 
     override fun requestFocus() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
index 07e143a..a1dd415 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
@@ -40,6 +40,7 @@
  */
 interface ScreenshotActionsProvider {
     fun onScrollChipReady(onClick: Runnable)
+    fun onScrollChipInvalidated()
     fun setCompletedScreenshot(result: ScreenshotSavedResult)
 
     /**
@@ -67,6 +68,8 @@
     @Assisted val requestId: String,
     @Assisted val actionExecutor: ActionExecutor,
 ) : ScreenshotActionsProvider {
+    private var addedScrollChip = false
+    private var onScrollClick: Runnable? = null
     private var pendingAction: ((ScreenshotSavedResult) -> Unit)? = null
     private var result: ScreenshotSavedResult? = null
 
@@ -87,7 +90,8 @@
                 AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_edit),
                 context.resources.getString(R.string.screenshot_edit_label),
                 context.resources.getString(R.string.screenshot_edit_description),
-            )
+            ),
+            showDuringEntrance = true,
         ) {
             debugLog(LogConfig.DEBUG_ACTIONS) { "Edit tapped" }
             uiEventLogger.log(SCREENSHOT_EDIT_TAPPED, 0, request.packageNameString)
@@ -105,7 +109,8 @@
                 AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_share),
                 context.resources.getString(R.string.screenshot_share_label),
                 context.resources.getString(R.string.screenshot_share_description),
-            )
+            ),
+            showDuringEntrance = true,
         ) {
             debugLog(LogConfig.DEBUG_ACTIONS) { "Share tapped" }
             uiEventLogger.log(SCREENSHOT_SHARE_TAPPED, 0, request.packageNameString)
@@ -120,17 +125,26 @@
     }
 
     override fun onScrollChipReady(onClick: Runnable) {
-        viewModel.addAction(
-            ActionButtonAppearance(
-                AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_scroll),
-                context.resources.getString(R.string.screenshot_scroll_label),
-                context.resources.getString(R.string.screenshot_scroll_label),
-            )
-        ) {
-            onClick.run()
+        onScrollClick = onClick
+        if (!addedScrollChip) {
+            viewModel.addAction(
+                ActionButtonAppearance(
+                    AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_scroll),
+                    context.resources.getString(R.string.screenshot_scroll_label),
+                    context.resources.getString(R.string.screenshot_scroll_label),
+                ),
+                showDuringEntrance = true,
+            ) {
+                onScrollClick?.run()
+            }
+            addedScrollChip = true
         }
     }
 
+    override fun onScrollChipInvalidated() {
+        onScrollClick = null
+    }
+
     override fun setCompletedScreenshot(result: ScreenshotSavedResult) {
         if (this.result != null) {
             Log.e(TAG, "Got a second completed screenshot for existing request!")
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 494fc9b..9ad6d0f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -47,7 +47,6 @@
 import android.graphics.Bitmap;
 import android.graphics.Insets;
 import android.graphics.Rect;
-import android.hardware.display.DisplayManager;
 import android.net.Uri;
 import android.os.Process;
 import android.os.UserHandle;
@@ -208,8 +207,7 @@
     @Nullable
     private final ScreenshotSoundController mScreenshotSoundController;
     private final PhoneWindow mWindow;
-    private final DisplayManager mDisplayManager;
-    private final int mDisplayId;
+    private final Display mDisplay;
     private final ScrollCaptureExecutor mScrollCaptureExecutor;
     private final ScreenshotNotificationSmartActionsProvider
             mScreenshotNotificationSmartActionsProvider;
@@ -249,7 +247,6 @@
     @AssistedInject
     ScreenshotController(
             Context context,
-            DisplayManager displayManager,
             WindowManager windowManager,
             FeatureFlags flags,
             ScreenshotViewProxy.Factory viewProxyFactory,
@@ -271,12 +268,13 @@
             AssistContentRequester assistContentRequester,
             MessageContainerController messageContainerController,
             Provider<ScreenshotSoundController> screenshotSoundController,
-            @Assisted int displayId,
+            @Assisted Display display,
             @Assisted boolean showUIOnExternalDisplay
     ) {
         mScreenshotSmartActions = screenshotSmartActions;
         mActionsProviderFactory = actionsProviderFactory;
-        mNotificationsController = screenshotNotificationsControllerFactory.create(displayId);
+        mNotificationsController = screenshotNotificationsControllerFactory.create(
+                display.getDisplayId());
         mUiEventLogger = uiEventLogger;
         mImageExporter = imageExporter;
         mImageCapture = imageCapture;
@@ -290,11 +288,9 @@
         mScreenshotHandler = timeoutHandler;
         mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
 
-
-        mDisplayId = displayId;
-        mDisplayManager = displayManager;
+        mDisplay = display;
         mWindowManager = windowManager;
-        final Context displayContext = context.createDisplayContext(getDisplay());
+        final Context displayContext = context.createDisplayContext(display);
         mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
         mFlags = flags;
         mActionIntentExecutor = actionIntentExecutor;
@@ -302,7 +298,7 @@
         mMessageContainerController = messageContainerController;
         mAssistContentRequester = assistContentRequester;
 
-        mViewProxy = viewProxyFactory.getProxy(mContext, mDisplayId);
+        mViewProxy = viewProxyFactory.getProxy(mContext, mDisplay.getDisplayId());
 
         mScreenshotHandler.setOnTimeoutRunnable(() -> {
             if (DEBUG_UI) {
@@ -321,14 +317,14 @@
         mConfigChanges.applyNewConfig(context.getResources());
         reloadAssets();
 
-        mActionExecutor = actionExecutorFactory.create(mWindow, mViewProxy.getScreenshotPreview(),
+        mActionExecutor = actionExecutorFactory.create(mWindow, mViewProxy,
                 () -> {
-                    requestDismissal(null);
+                    finishDismiss();
                     return Unit.INSTANCE;
                 });
 
         // Sound is only reproduced from the controller of the default display.
-        if (displayId == Display.DEFAULT_DISPLAY) {
+        if (mDisplay.getDisplayId() == Display.DEFAULT_DISPLAY) {
             mScreenshotSoundController = screenshotSoundController.get();
         } else {
             mScreenshotSoundController = null;
@@ -356,7 +352,7 @@
         if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN
                 && screenshot.getBitmap() == null) {
             Rect bounds = getFullScreenRect();
-            screenshot.setBitmap(mImageCapture.captureDisplay(mDisplayId, bounds));
+            screenshot.setBitmap(mImageCapture.captureDisplay(mDisplay.getDisplayId(), bounds));
             screenshot.setScreenBounds(bounds);
         }
 
@@ -459,7 +455,7 @@
     }
 
     private boolean shouldShowUi() {
-        return mDisplayId == Display.DEFAULT_DISPLAY || mShowUIOnExternalDisplay;
+        return mDisplay.getDisplayId() == Display.DEFAULT_DISPLAY || mShowUIOnExternalDisplay;
     }
 
     void prepareViewForNewScreenshot(@NonNull ScreenshotData screenshot, String oldPackageName) {
@@ -590,7 +586,11 @@
                             if (mConfigChanges.applyNewConfig(mContext.getResources())) {
                                 // Hide the scroll chip until we know it's available in this
                                 // orientation
-                                mViewProxy.hideScrollChip();
+                                if (screenshotShelfUi2()) {
+                                    mActionsProvider.onScrollChipInvalidated();
+                                } else {
+                                    mViewProxy.hideScrollChip();
+                                }
                                 // Delay scroll capture eval a bit to allow the underlying activity
                                 // to set up in the new orientation.
                                 mScreenshotHandler.postDelayed(
@@ -618,14 +618,16 @@
 
     private void requestScrollCapture(UserHandle owner) {
         mScrollCaptureExecutor.requestScrollCapture(
-                mDisplayId,
+                mDisplay.getDisplayId(),
                 mWindow.getDecorView().getWindowToken(),
                 (response) -> {
                     mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION,
                             0, response.getPackageName());
-                    if (screenshotShelfUi2() && mActionsProvider != null) {
-                        mActionsProvider.onScrollChipReady(
-                                () -> onScrollButtonClicked(owner, response));
+                    if (screenshotShelfUi2()) {
+                        if (mActionsProvider != null) {
+                            mActionsProvider.onScrollChipReady(
+                                    () -> onScrollButtonClicked(owner, response));
+                        }
                     } else {
                         mViewProxy.showScrollChip(response.getPackageName(),
                                 () -> onScrollButtonClicked(owner, response));
@@ -641,7 +643,8 @@
         }
         mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0,
                 response.getPackageName());
-        Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId, getFullScreenRect());
+        Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplay.getDisplayId(),
+                getFullScreenRect());
         if (newScreenshot == null) {
             Log.e(TAG, "Failed to capture current screenshot for scroll transition!");
             return;
@@ -656,9 +659,7 @@
                 () -> {
                     final Intent intent = ActionIntentCreator.INSTANCE.createLongScreenshotIntent(
                             owner, mContext);
-                    mActionIntentExecutor.launchIntentAsync(intent, owner, true,
-                            ActivityOptions.makeCustomAnimation(mContext, 0, 0), null);
-
+                    mActionIntentExecutor.launchIntentAsync(intent, owner, true, null, null);
                 },
                 mViewProxy::restoreNonScrollingUi,
                 mViewProxy::startLongScreenshotTransition);
@@ -819,7 +820,8 @@
     private void saveScreenshotInBackground(
             ScreenshotData screenshot, UUID requestId, Consumer<Uri> finisher) {
         ListenableFuture<ImageExporter.Result> future = mImageExporter.export(mBgExecutor,
-                requestId, screenshot.getBitmap(), screenshot.getUserOrDefault(), mDisplayId);
+                requestId, screenshot.getBitmap(), screenshot.getUserOrDefault(),
+                mDisplay.getDisplayId());
         future.addListener(() -> {
             try {
                 ImageExporter.Result result = future.get();
@@ -861,7 +863,7 @@
         data.mActionsReadyListener = actionsReadyListener;
         data.mQuickShareActionsReadyListener = quickShareActionsReadyListener;
         data.owner = owner;
-        data.displayId = mDisplayId;
+        data.displayId = mDisplay.getDisplayId();
 
         if (mSaveInBgTask != null) {
             // just log success/failure for the pre-existing screenshot
@@ -986,13 +988,9 @@
         }
     }
 
-    private Display getDisplay() {
-        return mDisplayManager.getDisplay(mDisplayId);
-    }
-
     private Rect getFullScreenRect() {
         DisplayMetrics displayMetrics = new DisplayMetrics();
-        getDisplay().getRealMetrics(displayMetrics);
+        mDisplay.getRealMetrics(displayMetrics);
         return new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels);
     }
 
@@ -1026,12 +1024,12 @@
     @AssistedFactory
     public interface Factory {
         /**
-         * Creates an instance of the controller for that specific displayId.
+         * Creates an instance of the controller for that specific display.
          *
-         * @param displayId:               display to capture
-         * @param showUIOnExternalDisplay: Whether the UI should be shown if this is an external
-         *                                 display.
+         * @param display                 display to capture
+         * @param showUIOnExternalDisplay Whether the UI should be shown if this is an external
+         *                                display.
          */
-        ScreenshotController create(int displayId, boolean showUIOnExternalDisplay);
+        ScreenshotController create(Display display, boolean showUIOnExternalDisplay);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotKeyguardController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotKeyguardController.kt
deleted file mode 100644
index 7696bbe..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotKeyguardController.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.screenshot
-
-import android.content.Context
-import android.content.Intent
-import com.android.internal.infra.ServiceConnector
-import javax.inject.Inject
-import kotlinx.coroutines.CompletableDeferred
-
-open class ScreenshotKeyguardController @Inject constructor(context: Context) {
-    private val proxyConnector: ServiceConnector<IScreenshotProxy> =
-        ServiceConnector.Impl(
-            context,
-            Intent(context, ScreenshotProxyService::class.java),
-            Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
-            context.userId,
-            IScreenshotProxy.Stub::asInterface
-        )
-
-    suspend fun dismiss() {
-        val completion = CompletableDeferred<Unit>()
-        val onDoneBinder =
-            object : IOnDoneCallback.Stub() {
-                override fun onDone(success: Boolean) {
-                    completion.complete(Unit)
-                }
-            }
-        proxyConnector.post { it.dismissKeyguard(onDoneBinder) }
-        completion.await()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
index 9b5e7182..3ac070a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
@@ -31,6 +31,7 @@
 import android.view.WindowManager
 import android.window.OnBackInvokedCallback
 import android.window.OnBackInvokedDispatcher
+import androidx.appcompat.content.res.AppCompatResources
 import androidx.core.animation.doOnEnd
 import androidx.core.animation.doOnStart
 import com.android.internal.logging.UiEventLogger
@@ -45,6 +46,7 @@
 import com.android.systemui.screenshot.ui.ScreenshotAnimationController
 import com.android.systemui.screenshot.ui.ScreenshotShelfView
 import com.android.systemui.screenshot.ui.binder.ScreenshotShelfViewBinder
+import com.android.systemui.screenshot.ui.viewmodel.AnimationState
 import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
@@ -57,6 +59,7 @@
     private val logger: UiEventLogger,
     private val viewModel: ScreenshotViewModel,
     private val windowManager: WindowManager,
+    shelfViewBinder: ScreenshotShelfViewBinder,
     private val thumbnailObserver: ThumbnailObserver,
     @Assisted private val context: Context,
     @Assisted private val displayId: Int
@@ -68,7 +71,17 @@
     override var callbacks: ScreenshotView.ScreenshotViewCallback? = null
     override var screenshot: ScreenshotData? = null
         set(value) {
-            viewModel.setScreenshotBitmap(value?.bitmap)
+            value?.let {
+                viewModel.setScreenshotBitmap(it.bitmap)
+                val badgeBg =
+                    AppCompatResources.getDrawable(context, R.drawable.overlay_badge_background)
+                val user = it.userHandle
+                if (badgeBg != null && user != null) {
+                    viewModel.setScreenshotBadge(
+                        context.packageManager.getUserBadgedIcon(badgeBg, user)
+                    )
+                }
+            }
             field = value
         }
 
@@ -77,15 +90,16 @@
     override var isDismissing = false
     override var isPendingSharedTransition = false
 
-    private val animationController = ScreenshotAnimationController(view)
+    private val animationController = ScreenshotAnimationController(view, viewModel)
 
     init {
-        ScreenshotShelfViewBinder.bind(
+        shelfViewBinder.bind(
             view,
             viewModel,
+            animationController,
             LayoutInflater.from(context),
             onDismissalRequested = { event, velocity -> requestDismissal(event, velocity) },
-            onDismissalCancelled = { animationController.getSwipeReturnAnimation().start() }
+            onUserInteraction = { callbacks?.onUserInteraction() }
         )
         view.updateInsets(windowManager.currentWindowMetrics.windowInsets)
         addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
@@ -119,12 +133,19 @@
     override fun updateOrientation(insets: WindowInsets) {}
 
     override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator {
-        val entrance = animationController.getEntranceAnimation(screenRect, showFlash)
-        entrance.doOnStart { thumbnailObserver.onEntranceStarted() }
+        val entrance =
+            animationController.getEntranceAnimation(screenRect, showFlash) {
+                viewModel.setAnimationState(AnimationState.ENTRANCE_REVEAL)
+            }
+        entrance.doOnStart {
+            thumbnailObserver.onEntranceStarted()
+            viewModel.setAnimationState(AnimationState.ENTRANCE_STARTED)
+        }
         entrance.doOnEnd {
             // reset the timeout when animation finishes
             callbacks?.onUserInteraction()
             thumbnailObserver.onEntranceComplete()
+            viewModel.setAnimationState(AnimationState.ENTRANCE_COMPLETE)
         }
         return entrance
     }
@@ -167,24 +188,53 @@
 
     override fun prepareScrollingTransition(
         response: ScrollCaptureResponse,
-        screenBitmap: Bitmap,
+        screenBitmap: Bitmap, // unused
         newScreenshot: Bitmap,
         screenshotTakenInPortrait: Boolean,
         onTransitionPrepared: Runnable,
     ) {
-        onTransitionPrepared.run()
+        viewModel.setScrollingScrimBitmap(newScreenshot)
+        viewModel.setScrollableRect(scrollableAreaOnScreen(response))
+        animationController.fadeForLongScreenshotTransition()
+        view.post { onTransitionPrepared.run() }
+    }
+
+    private fun scrollableAreaOnScreen(response: ScrollCaptureResponse): Rect {
+        val r = Rect(response.boundsInWindow)
+        val windowInScreen = response.windowBounds
+        r.offset(windowInScreen?.left ?: 0, windowInScreen?.top ?: 0)
+        r.intersect(
+            Rect(
+                0,
+                0,
+                context.resources.displayMetrics.widthPixels,
+                context.resources.displayMetrics.heightPixels
+            )
+        )
+        return r
     }
 
     override fun startLongScreenshotTransition(
         transitionDestination: Rect,
         onTransitionEnd: Runnable,
-        longScreenshot: ScrollCaptureController.LongScreenshot
+        longScreenshot: ScrollCaptureController.LongScreenshot,
     ) {
-        onTransitionEnd.run()
-        callbacks?.onDismiss()
+        val transitionAnimation =
+            animationController.runLongScreenshotTransition(
+                transitionDestination,
+                longScreenshot,
+                onTransitionEnd
+            )
+        transitionAnimation.doOnEnd { callbacks?.onDismiss() }
+        transitionAnimation.start()
     }
 
-    override fun restoreNonScrollingUi() {}
+    override fun restoreNonScrollingUi() {
+        viewModel.setScrollableRect(null)
+        viewModel.setScrollingScrimBitmap(null)
+        animationController.restoreUI()
+        callbacks?.onUserInteraction() // reset the timeout
+    }
 
     override fun stopInputListening() {}
 
@@ -207,6 +257,10 @@
         )
     }
 
+    override fun fadeForSharedTransition() {
+        animationController.fadeForSharedTransition()
+    }
+
     private fun addPredictiveBackListener(onDismissRequested: (ScreenshotEvent) -> Unit) {
         val onBackInvokedCallback = OnBackInvokedCallback {
             debugLog(DEBUG_INPUT) { "Predictive Back callback dispatched" }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
index a4069d1..df93a5e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
@@ -63,6 +63,7 @@
         longScreenshot: ScrollCaptureController.LongScreenshot
     )
     fun restoreNonScrollingUi()
+    fun fadeForSharedTransition()
 
     fun stopInputListening()
     fun requestFocus()
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index e56a4f4..40d709d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -68,11 +68,13 @@
         onSaved: (Uri?) -> Unit,
         requestCallback: RequestCallback
     ) {
-        val displayIds = getDisplaysToScreenshot(screenshotRequest.type)
+        val displays = getDisplaysToScreenshot(screenshotRequest.type)
         val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback)
-        displayIds.forEach { displayId: Int ->
+        displays.forEach { display ->
+            val displayId = display.displayId
             Log.d(TAG, "Executing screenshot for display $displayId")
             dispatchToController(
+                display = display,
                 rawScreenshotData = ScreenshotData.fromRequest(screenshotRequest, displayId),
                 onSaved =
                     if (displayId == Display.DEFAULT_DISPLAY) {
@@ -85,6 +87,7 @@
 
     /** All logging should be triggered only by this method. */
     private suspend fun dispatchToController(
+        display: Display,
         rawScreenshotData: ScreenshotData,
         onSaved: (Uri?) -> Unit,
         callback: RequestCallback
@@ -104,8 +107,7 @@
         logScreenshotRequested(screenshotData)
         Log.d(TAG, "Screenshot request: $screenshotData")
         try {
-            getScreenshotController(screenshotData.displayId)
-                .handleScreenshot(screenshotData, onSaved, callback)
+            getScreenshotController(display).handleScreenshot(screenshotData, onSaved, callback)
         } catch (e: IllegalStateException) {
             Log.e(TAG, "Error while ScreenshotController was handling ScreenshotData!", e)
             onFailedScreenshotRequest(screenshotData, callback)
@@ -135,12 +137,13 @@
         callback.reportError()
     }
 
-    private suspend fun getDisplaysToScreenshot(requestType: Int): List<Int> {
+    private suspend fun getDisplaysToScreenshot(requestType: Int): List<Display> {
+        val allDisplays = displays.first()
         return if (requestType == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
             // If this is a provided image, let's show the UI on the default display only.
-            listOf(Display.DEFAULT_DISPLAY)
+            allDisplays.filter { it.displayId == Display.DEFAULT_DISPLAY }
         } else {
-            displays.first().filter { it.type in ALLOWED_DISPLAY_TYPES }.map { it.displayId }
+            allDisplays.filter { it.type in ALLOWED_DISPLAY_TYPES }
         }
     }
 
@@ -170,9 +173,9 @@
         screenshotControllers.clear()
     }
 
-    private fun getScreenshotController(id: Int): ScreenshotController {
-        return screenshotControllers.computeIfAbsent(id) {
-            screenshotControllerFactory.create(id, /* showUIOnExternalDisplay= */ false)
+    private fun getScreenshotController(display: Display): ScreenshotController {
+        return screenshotControllers.computeIfAbsent(display.displayId) {
+            screenshotControllerFactory.create(display, /* showUIOnExternalDisplay= */ false)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
index 5e561cf..ee1944e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
@@ -45,6 +45,7 @@
 import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
 
 import com.android.internal.graphics.ColorUtils;
+import com.android.systemui.Flags;
 import com.android.systemui.res.R;
 
 import java.util.List;
@@ -378,8 +379,14 @@
                 upper = 1;
                 break;
         }
-        Log.i(TAG, "getAllowedValues: " + boundary + ", "
-                + "result=[lower=" + lower + ", upper=" + upper + "]");
+        if (lower >= upper) {
+            Log.wtf(TAG, "getAllowedValues computed an invalid range "
+                    + "[" + lower + ", " + upper + "]");
+            if (Flags.screenshotScrollCropViewCrashFix()) {
+                lower = Math.min(lower, upper);
+                upper = lower;
+            }
+        }
         return new Range<>(lower, upper);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
index da26830..a4906c1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
@@ -20,6 +20,10 @@
 import android.animation.AnimatorSet
 import android.animation.ObjectAnimator
 import android.animation.ValueAnimator
+import android.content.res.ColorStateList
+import android.graphics.BlendMode
+import android.graphics.Color
+import android.graphics.Matrix
 import android.graphics.PointF
 import android.graphics.Rect
 import android.util.MathUtils
@@ -29,13 +33,21 @@
 import androidx.core.animation.doOnEnd
 import androidx.core.animation.doOnStart
 import com.android.systemui.res.R
+import com.android.systemui.screenshot.scroll.ScrollCaptureController
+import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
 import kotlin.math.abs
 import kotlin.math.max
 import kotlin.math.sign
 
-class ScreenshotAnimationController(private val view: ScreenshotShelfView) {
+class ScreenshotAnimationController(
+    private val view: ScreenshotShelfView,
+    private val viewModel: ScreenshotViewModel
+) {
     private var animator: Animator? = null
     private val screenshotPreview = view.requireViewById<ImageView>(R.id.screenshot_preview)
+    private val scrollingScrim = view.requireViewById<ImageView>((R.id.screenshot_scrolling_scrim))
+    private val scrollTransitionPreview =
+        view.requireViewById<ImageView>(R.id.screenshot_scrollable_preview)
     private val flashView = view.requireViewById<View>(R.id.screenshot_flash)
     private val actionContainer = view.requireViewById<View>(R.id.actions_container_background)
     private val fastOutSlowIn =
@@ -46,8 +58,20 @@
             view.requireViewById(R.id.screenshot_badge),
             view.requireViewById(R.id.screenshot_dismiss_button)
         )
+    private val fadeUI =
+        listOf<View>(
+            view.requireViewById(R.id.screenshot_preview_border),
+            view.requireViewById(R.id.actions_container_background),
+            view.requireViewById(R.id.screenshot_badge),
+            view.requireViewById(R.id.screenshot_dismiss_button),
+            view.requireViewById(R.id.screenshot_message_container),
+        )
 
-    fun getEntranceAnimation(bounds: Rect, showFlash: Boolean): Animator {
+    fun getEntranceAnimation(
+        bounds: Rect,
+        showFlash: Boolean,
+        onRevealMilestone: () -> Unit
+    ): Animator {
         val entranceAnimation = AnimatorSet()
 
         val previewAnimator = getPreviewAnimator(bounds)
@@ -70,7 +94,19 @@
             entranceAnimation.doOnStart { screenshotPreview.visibility = View.INVISIBLE }
         }
 
-        entranceAnimation.play(getActionsAnimator()).with(previewAnimator)
+        val actionsAnimator = getActionsAnimator()
+        entranceAnimation.play(actionsAnimator).with(previewAnimator)
+
+        // This isn't actually animating anything but is basically a timer for the first 200ms of
+        // the entrance animation. Using an animator here ensures that this is scaled if we change
+        // animator duration scales.
+        val revealMilestoneAnimator =
+            ValueAnimator.ofFloat(0f).apply {
+                duration = 0
+                startDelay = ACTION_REVEAL_DELAY_MS
+                doOnEnd { onRevealMilestone() }
+            }
+        entranceAnimation.play(revealMilestoneAnimator).with(actionsAnimator)
 
         val fadeInAnimator = ValueAnimator.ofFloat(0f, 1f)
         fadeInAnimator.addUpdateListener {
@@ -80,15 +116,108 @@
         }
         entranceAnimation.play(fadeInAnimator).after(previewAnimator)
         entranceAnimation.doOnStart {
+            viewModel.setIsAnimating(true)
             for (child in staticUI) {
                 child.alpha = 0f
             }
         }
+        entranceAnimation.doOnEnd { viewModel.setIsAnimating(false) }
 
         this.animator = entranceAnimation
         return entranceAnimation
     }
 
+    fun fadeForSharedTransition() {
+        animator?.cancel()
+        val fadeAnimator = ValueAnimator.ofFloat(1f, 0f)
+        fadeAnimator.addUpdateListener {
+            for (view in fadeUI) {
+                view.alpha = it.animatedValue as Float
+            }
+        }
+        animator = fadeAnimator
+        fadeAnimator.start()
+    }
+
+    fun runLongScreenshotTransition(
+        destRect: Rect,
+        longScreenshot: ScrollCaptureController.LongScreenshot,
+        onTransitionEnd: Runnable
+    ): Animator {
+        val animSet = AnimatorSet()
+
+        val scrimAnim = ValueAnimator.ofFloat(0f, 1f)
+        scrimAnim.addUpdateListener { animation: ValueAnimator ->
+            scrollingScrim.setAlpha(1 - animation.animatedFraction)
+        }
+        scrollTransitionPreview.visibility = View.VISIBLE
+        if (true) {
+            scrollTransitionPreview.setImageBitmap(longScreenshot.toBitmap())
+            val startX: Float = scrollTransitionPreview.x
+            val startY: Float = scrollTransitionPreview.y
+            val locInScreen: IntArray = scrollTransitionPreview.getLocationOnScreen()
+            destRect.offset(startX.toInt() - locInScreen[0], startY.toInt() - locInScreen[1])
+            scrollTransitionPreview.pivotX = 0f
+            scrollTransitionPreview.pivotY = 0f
+            scrollTransitionPreview.setAlpha(1f)
+            val currentScale: Float = scrollTransitionPreview.width / longScreenshot.width.toFloat()
+            val matrix = Matrix()
+            matrix.setScale(currentScale, currentScale)
+            matrix.postTranslate(
+                longScreenshot.left * currentScale,
+                longScreenshot.top * currentScale
+            )
+            scrollTransitionPreview.setImageMatrix(matrix)
+            val destinationScale: Float = destRect.width() / scrollTransitionPreview.width.toFloat()
+            val previewAnim = ValueAnimator.ofFloat(0f, 1f)
+            previewAnim.addUpdateListener { animation: ValueAnimator ->
+                val t = animation.animatedFraction
+                val currScale = MathUtils.lerp(1f, destinationScale, t)
+                scrollTransitionPreview.scaleX = currScale
+                scrollTransitionPreview.scaleY = currScale
+                scrollTransitionPreview.x = MathUtils.lerp(startX, destRect.left.toFloat(), t)
+                scrollTransitionPreview.y = MathUtils.lerp(startY, destRect.top.toFloat(), t)
+            }
+            val previewFadeAnim = ValueAnimator.ofFloat(1f, 0f)
+            previewFadeAnim.addUpdateListener { animation: ValueAnimator ->
+                scrollTransitionPreview.setAlpha(1 - animation.animatedFraction)
+            }
+            previewAnim.doOnEnd { onTransitionEnd.run() }
+            animSet.play(previewAnim).with(scrimAnim).before(previewFadeAnim)
+        } else {
+            // if we switched orientations between the original screenshot and the long screenshot
+            // capture, just fade out the scrim instead of running the preview animation
+            scrimAnim.doOnEnd { onTransitionEnd.run() }
+            animSet.play(scrimAnim)
+        }
+        animator = animSet
+        return animSet
+    }
+
+    fun fadeForLongScreenshotTransition() {
+        scrollingScrim.imageTintBlendMode = BlendMode.SRC_ATOP
+        val anim = ValueAnimator.ofFloat(0f, .3f)
+        anim.addUpdateListener {
+            scrollingScrim.setImageTintList(
+                ColorStateList.valueOf(Color.argb(it.animatedValue as Float, 0f, 0f, 0f))
+            )
+        }
+        for (view in fadeUI) {
+            view.alpha = 0f
+        }
+        screenshotPreview.alpha = 0f
+        anim.setDuration(200)
+        anim.start()
+    }
+
+    fun restoreUI() {
+        animator?.cancel()
+        for (view in fadeUI) {
+            view.alpha = 1f
+        }
+        screenshotPreview.alpha = 1f
+    }
+
     fun getSwipeReturnAnimation(): Animator {
         animator?.cancel()
         val animator = ValueAnimator.ofFloat(view.translationX, 0f)
@@ -98,6 +227,7 @@
     }
 
     fun getSwipeDismissAnimation(requestedVelocity: Float?): Animator {
+        animator?.cancel()
         val velocity = getAdjustedVelocity(requestedVelocity)
         val screenWidth = view.resources.displayMetrics.widthPixels
         // translation at which point the visible UI is fully off the screen (in the direction
@@ -115,6 +245,8 @@
             view.alpha = 1f - it.animatedFraction
         }
         animator.duration = ((abs(distance / velocity))).toLong()
+        animator.doOnStart { viewModel.setIsAnimating(true) }
+        animator.doOnEnd { viewModel.setIsAnimating(false) }
 
         this.animator = animator
         return animator
@@ -198,5 +330,6 @@
         private const val FLASH_OUT_DURATION_MS: Long = 217
         private const val PREVIEW_X_ANIMATION_DURATION_MS: Long = 234
         private const val PREVIEW_Y_ANIMATION_DURATION_MS: Long = 500
+        private const val ACTION_REVEAL_DELAY_MS: Long = 200
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
index 916d50f..969cf48 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
@@ -22,6 +22,8 @@
 import android.graphics.Rect
 import android.graphics.Region
 import android.util.AttributeSet
+import android.view.GestureDetector
+import android.view.GestureDetector.SimpleOnGestureListener
 import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
@@ -30,7 +32,6 @@
 import android.widget.ImageView
 import com.android.systemui.res.R
 import com.android.systemui.screenshot.FloatingWindowUtil
-import kotlin.math.max
 
 class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) :
     FrameLayout(context, attrs) {
@@ -39,11 +40,50 @@
     private lateinit var screenshotStatic: ViewGroup
     var onTouchInterceptListener: ((MotionEvent) -> Boolean)? = null
 
+    var userInteractionCallback: (() -> Unit)? = null
+
     private val displayMetrics = context.resources.displayMetrics
     private val tmpRect = Rect()
     private lateinit var actionsContainerBackground: View
+    private lateinit var actionsContainer: View
     private lateinit var dismissButton: View
 
+    // Prepare an internal `GestureDetector` to determine when we can initiate a touch-interception
+    // session (with the client's provided `onTouchInterceptListener`). We delegate out to their
+    // listener only for gestures that can't be handled by scrolling our `actionsContainer`.
+    private val gestureDetector =
+        GestureDetector(
+            context,
+            object : SimpleOnGestureListener() {
+                override fun onScroll(
+                    ev1: MotionEvent?,
+                    ev2: MotionEvent,
+                    distanceX: Float,
+                    distanceY: Float
+                ): Boolean {
+                    actionsContainer.getBoundsOnScreen(tmpRect)
+                    val touchedInActionsContainer =
+                        tmpRect.contains(ev2.rawX.toInt(), ev2.rawY.toInt())
+                    val canHandleInternallyByScrolling =
+                        touchedInActionsContainer
+                        && actionsContainer.canScrollHorizontally(distanceX.toInt())
+                    return !canHandleInternallyByScrolling
+                }
+            }
+        )
+
+    init {
+
+        // Delegate to the client-provided `onTouchInterceptListener` if we've already initiated
+        // touch-interception.
+        setOnTouchListener({ _: View, ev: MotionEvent ->
+            userInteractionCallback?.invoke()
+            onTouchInterceptListener?.invoke(ev) ?: false
+        })
+
+        gestureDetector.setIsLongpressEnabled(false)
+    }
+
     override fun onFinishInflate() {
         super.onFinishInflate()
         // Get focus so that the key events go to the layout.
@@ -52,7 +92,15 @@
         blurredScreenshotPreview = requireViewById(R.id.screenshot_preview_blur)
         screenshotStatic = requireViewById(R.id.screenshot_static)
         actionsContainerBackground = requireViewById(R.id.actions_container_background)
+        actionsContainer = requireViewById(R.id.actions_container)
         dismissButton = requireViewById(R.id.screenshot_dismiss_button)
+
+        // Configure to extend the timeout during ongoing gestures (i.e. scrolls) that are already
+        // being handled by our child views.
+        actionsContainer.setOnTouchListener({ _: View, ev: MotionEvent ->
+            userInteractionCallback?.invoke()
+            false
+        })
     }
 
     fun getTouchRegion(gestureInsets: Insets): Region {
@@ -79,6 +127,18 @@
         val inPortrait = orientation == Configuration.ORIENTATION_PORTRAIT
         val cutout = insets.displayCutout
         val navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars())
+
+        // When honoring the navbar or other obstacle offsets, include some extra padding above
+        // the inset itself.
+        val verticalPadding =
+            mContext.resources.getDimensionPixelOffset(R.dimen.screenshot_shelf_vertical_margin)
+
+        // Minimum bottom padding to always enforce (e.g. if there's no nav bar)
+        val minimumBottomPadding =
+            context.resources.getDimensionPixelOffset(
+                R.dimen.overlay_action_container_minimum_edge_spacing
+            )
+
         if (cutout == null) {
             screenshotStatic.setPadding(0, 0, 0, navBarInsets.bottom)
         } else {
@@ -86,25 +146,41 @@
             if (inPortrait) {
                 screenshotStatic.setPadding(
                     waterfall.left,
-                    max(cutout.safeInsetTop.toDouble(), waterfall.top.toDouble()).toInt(),
+                    max(cutout.safeInsetTop, waterfall.top),
                     waterfall.right,
                     max(
-                            cutout.safeInsetBottom.toDouble(),
-                            max(navBarInsets.bottom.toDouble(), waterfall.bottom.toDouble())
-                        )
-                        .toInt()
+                        navBarInsets.bottom + verticalPadding,
+                        cutout.safeInsetBottom + verticalPadding,
+                        waterfall.bottom + verticalPadding,
+                        minimumBottomPadding,
+                    )
                 )
             } else {
                 screenshotStatic.setPadding(
-                    max(cutout.safeInsetLeft.toDouble(), waterfall.left.toDouble()).toInt(),
+                    max(cutout.safeInsetLeft, waterfall.left),
                     waterfall.top,
-                    max(cutout.safeInsetRight.toDouble(), waterfall.right.toDouble()).toInt(),
-                    max(navBarInsets.bottom.toDouble(), waterfall.bottom.toDouble()).toInt()
+                    max(cutout.safeInsetRight, waterfall.right),
+                    max(
+                        navBarInsets.bottom + verticalPadding,
+                        waterfall.bottom + verticalPadding,
+                        minimumBottomPadding,
+                    )
                 )
             }
         }
     }
 
+    // Max function for two or more params.
+    private fun max(first: Int, second: Int, vararg items: Int): Int {
+        var largest = if (first > second) first else second
+        for (item in items) {
+            if (item > largest) {
+                largest = item
+            }
+        }
+        return largest
+    }
+
     private fun getSwipeRegion(): Region {
         val swipeRegion = Region()
         val padding = FloatingWindowUtil.dpToPx(displayMetrics, -1 * TOUCH_PADDING_DP).toInt()
@@ -127,10 +203,24 @@
         private const val TOUCH_PADDING_DP = 12f
     }
 
+    override fun onInterceptHoverEvent(event: MotionEvent): Boolean {
+        userInteractionCallback?.invoke()
+        return super.onInterceptHoverEvent(event)
+    }
+
     override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
-        if (onTouchInterceptListener?.invoke(ev) == true) {
-            return true
+        userInteractionCallback?.invoke()
+
+        // Let the client-provided listener see all `DOWN` events so that they'll be able to
+        // interpret the remainder of the gesture, even if interception starts partway-through.
+        // TODO: is this really necessary? And if we don't go on to start interception, should we
+        // follow up with `ACTION_CANCEL`?
+        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            onTouchInterceptListener?.invoke(ev)
         }
-        return super.onInterceptTouchEvent(ev)
+
+        // Only allow the client-provided touch interceptor to take over the gesture if our
+        // top-level `GestureDetector` decides not to scroll the action container.
+        return gestureDetector.onTouchEvent(ev)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/TransitioningIconDrawable.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/TransitioningIconDrawable.kt
new file mode 100644
index 0000000..0bc280c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/TransitioningIconDrawable.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.screenshot.ui
+
+import android.animation.ValueAnimator
+import android.content.res.ColorStateList
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.drawable.Drawable
+import androidx.core.animation.doOnEnd
+import java.util.Objects
+
+/**  */
+class TransitioningIconDrawable : Drawable() {
+    // The drawable for the current icon of this view. During icon transitions, this is the one
+    // being animated out.
+    private var drawable: Drawable? = null
+
+    // The incoming new icon. Only populated during transition animations (when drawable is also
+    // non-null).
+    private var enteringDrawable: Drawable? = null
+    private var colorFilter: ColorFilter? = null
+    private var tint: ColorStateList? = null
+    private var alpha = 255
+
+    private var transitionAnimator =
+        ValueAnimator.ofFloat(0f, 1f).also { it.doOnEnd { onTransitionComplete() } }
+
+    /**
+     * Set the drawable to be displayed, potentially animating the transition from one icon to the
+     * next.
+     */
+    fun setIcon(incomingDrawable: Drawable?) {
+        if (Objects.equals(drawable, incomingDrawable) && !transitionAnimator.isRunning) {
+            return
+        }
+
+        incomingDrawable?.colorFilter = colorFilter
+        incomingDrawable?.setTintList(tint)
+
+        if (drawable == null) {
+            // No existing icon drawn, just show the new one without a transition
+            drawable = incomingDrawable
+            invalidateSelf()
+            return
+        }
+
+        if (enteringDrawable != null) {
+            // There's already an entrance animation happening, just update the entering icon, not
+            // maintaining a queue or anything.
+            enteringDrawable = incomingDrawable
+            return
+        }
+
+        // There was already an icon, need to animate between icons.
+        enteringDrawable = incomingDrawable
+        transitionAnimator.setCurrentFraction(0f)
+        transitionAnimator.start()
+        invalidateSelf()
+    }
+
+    override fun draw(canvas: Canvas) {
+        // Scale the old one down, scale the new one up.
+        drawable?.let {
+            val scale =
+                if (transitionAnimator.isRunning) {
+                    1f - transitionAnimator.animatedFraction
+                } else {
+                    1f
+                }
+            drawScaledDrawable(it, canvas, scale)
+        }
+        enteringDrawable?.let {
+            val scale = transitionAnimator.animatedFraction
+            drawScaledDrawable(it, canvas, scale)
+        }
+
+        if (transitionAnimator.isRunning) {
+            invalidateSelf()
+        }
+    }
+
+    private fun drawScaledDrawable(drawable: Drawable, canvas: Canvas, scale: Float) {
+        drawable.bounds = getBounds()
+        canvas.save()
+        canvas.scale(
+            scale,
+            scale,
+            (drawable.intrinsicWidth / 2).toFloat(),
+            (drawable.intrinsicHeight / 2).toFloat()
+        )
+        drawable.draw(canvas)
+        canvas.restore()
+    }
+
+    private fun onTransitionComplete() {
+        drawable = enteringDrawable
+        enteringDrawable = null
+        invalidateSelf()
+    }
+
+    override fun setTintList(tint: ColorStateList?) {
+        super.setTintList(tint)
+        drawable?.setTintList(tint)
+        enteringDrawable?.setTintList(tint)
+        this.tint = tint
+    }
+
+    override fun setAlpha(alpha: Int) {
+        this.alpha = alpha
+    }
+
+    override fun setColorFilter(colorFilter: ColorFilter?) {
+        this.colorFilter = colorFilter
+        drawable?.colorFilter = colorFilter
+        enteringDrawable?.colorFilter = colorFilter
+    }
+
+    override fun getOpacity(): Int = alpha
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
index 3c5a0ec..36aa39f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
@@ -21,15 +21,35 @@
 import android.widget.LinearLayout
 import android.widget.TextView
 import com.android.systemui.res.R
+import com.android.systemui.screenshot.ui.TransitioningIconDrawable
 import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel
+import javax.inject.Inject
 
-object ActionButtonViewBinder {
+class ActionButtonViewBinder @Inject constructor() {
     /** Binds the given view to the given view-model */
     fun bind(view: View, viewModel: ActionButtonViewModel) {
         val iconView = view.requireViewById<ImageView>(R.id.overlay_action_chip_icon)
         val textView = view.requireViewById<TextView>(R.id.overlay_action_chip_text)
+        if (iconView.drawable == null) {
+            iconView.setImageDrawable(TransitioningIconDrawable())
+        }
+        val drawable = iconView.drawable as? TransitioningIconDrawable
+        // Note we never re-bind a view to a different ActionButtonViewModel, different view
+        // models would remove/create separate views.
+        drawable?.setIcon(viewModel.appearance.icon)
         iconView.setImageDrawable(viewModel.appearance.icon)
+        if (!viewModel.appearance.tint) {
+            iconView.setImageTintList(null)
+        }
         textView.text = viewModel.appearance.label
+
+        viewModel.appearance.customBackground?.also {
+            if (it.canApplyTheme()) {
+                it.applyTheme(view.rootView.context.theme)
+            }
+            view.background = it
+        }
+
         setMargins(iconView, textView, viewModel.appearance.label?.isNotEmpty() ?: false)
         if (viewModel.onClicked != null) {
             view.setOnClickListener { viewModel.onClicked.invoke() }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
index bc35e6b..442b387 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
@@ -16,7 +16,11 @@
 
 package com.android.systemui.screenshot.ui.binder
 
+import android.content.res.Configuration
 import android.graphics.Bitmap
+import android.graphics.Matrix
+import android.graphics.Rect
+import android.util.LayoutDirection
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
@@ -29,20 +33,27 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
 import com.android.systemui.screenshot.ScreenshotEvent
+import com.android.systemui.screenshot.ui.ScreenshotAnimationController
 import com.android.systemui.screenshot.ui.ScreenshotShelfView
 import com.android.systemui.screenshot.ui.SwipeGestureListener
+import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel
+import com.android.systemui.screenshot.ui.viewmodel.AnimationState
 import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
 import com.android.systemui.util.children
+import javax.inject.Inject
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 
-object ScreenshotShelfViewBinder {
+class ScreenshotShelfViewBinder
+@Inject
+constructor(private val buttonViewBinder: ActionButtonViewBinder) {
     fun bind(
         view: ScreenshotShelfView,
         viewModel: ScreenshotViewModel,
+        animationController: ScreenshotAnimationController,
         layoutInflater: LayoutInflater,
         onDismissalRequested: (event: ScreenshotEvent, velocity: Float?) -> Unit,
-        onDismissalCancelled: () -> Unit,
+        onUserInteraction: () -> Unit
     ) {
         val swipeGestureListener =
             SwipeGestureListener(
@@ -50,9 +61,10 @@
                 onDismiss = {
                     onDismissalRequested(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, it)
                 },
-                onCancel = onDismissalCancelled
+                onCancel = { animationController.getSwipeReturnAnimation().start() }
             )
         view.onTouchInterceptListener = { swipeGestureListener.onMotionEvent(it) }
+        view.userInteractionCallback = onUserInteraction
 
         val previewView: ImageView = view.requireViewById(R.id.screenshot_preview)
         val previewViewBlur: ImageView = view.requireViewById(R.id.screenshot_preview_blur)
@@ -65,6 +77,9 @@
         dismissButton.setOnClickListener {
             onDismissalRequested(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL, null)
         }
+        val scrollingScrim: ImageView = view.requireViewById(R.id.screenshot_scrolling_scrim)
+        val scrollablePreview: ImageView = view.requireViewById(R.id.screenshot_scrollable_preview)
+        val badgeView = view.requireViewById<ImageView>(R.id.screenshot_badge)
 
         // use immediate dispatcher to ensure screenshot bitmap is set before animation
         view.repeatWhenAttached(Dispatchers.Main.immediate) {
@@ -84,57 +99,119 @@
                         }
                     }
                     launch {
+                        viewModel.scrollingScrim.collect { bitmap ->
+                            if (bitmap != null) {
+                                scrollingScrim.setImageBitmap(bitmap)
+                                scrollingScrim.visibility = View.VISIBLE
+                            } else {
+                                scrollingScrim.visibility = View.GONE
+                            }
+                        }
+                    }
+                    launch {
+                        viewModel.scrollableRect.collect { rect ->
+                            if (rect != null) {
+                                setScrollablePreview(
+                                    scrollablePreview,
+                                    viewModel.preview.value,
+                                    rect
+                                )
+                            } else {
+                                scrollablePreview.visibility = View.GONE
+                            }
+                        }
+                    }
+                    launch {
+                        viewModel.badge.collect { badge ->
+                            badgeView.setImageDrawable(badge)
+                            badgeView.visibility = if (badge != null) View.VISIBLE else View.GONE
+                        }
+                    }
+                    launch {
                         viewModel.previewAction.collect { onClick ->
                             previewView.setOnClickListener { onClick?.invoke() }
                         }
                     }
                     launch {
-                        viewModel.actions.collect { actions ->
-                            val visibleActions = actions.filter { it.visible }
-
-                            if (visibleActions.isNotEmpty()) {
-                                view
-                                    .requireViewById<View>(R.id.actions_container_background)
-                                    .visibility = View.VISIBLE
-                            }
-
-                            // Remove any buttons not in the new list, then do another pass to add
-                            // any new actions and update any that are already there.
-                            // This assumes that actions can never change order and that each action
-                            // ID is unique.
-                            val newIds = visibleActions.map { it.id }
-
-                            for (child in actionsContainer.children.toList()) {
-                                if (child.tag !in newIds) {
-                                    actionsContainer.removeView(child)
-                                }
-                            }
-
-                            for ((index, action) in visibleActions.withIndex()) {
-                                val currentView: View? = actionsContainer.getChildAt(index)
-                                if (action.id == currentView?.tag) {
-                                    // Same ID, update the display
-                                    ActionButtonViewBinder.bind(currentView, action)
-                                } else {
-                                    // Different ID. Removals have already happened so this must
-                                    // mean that the new action must be inserted here.
-                                    val actionButton =
-                                        layoutInflater.inflate(
-                                            R.layout.shelf_action_chip,
-                                            actionsContainer,
-                                            false
-                                        )
-                                    actionsContainer.addView(actionButton, index)
-                                    ActionButtonViewBinder.bind(actionButton, action)
-                                }
+                        viewModel.isAnimating.collect { isAnimating ->
+                            previewView.isClickable = !isAnimating
+                            for (child in actionsContainer.children) {
+                                child.isClickable = !isAnimating
                             }
                         }
                     }
+                    launch {
+                        viewModel.actions.collect { actions ->
+                            updateActions(
+                                actions,
+                                viewModel.animationState.value,
+                                view,
+                                layoutInflater
+                            )
+                        }
+                    }
+                    launch {
+                        viewModel.animationState.collect { animationState ->
+                            updateActions(
+                                viewModel.actions.value,
+                                animationState,
+                                view,
+                                layoutInflater
+                            )
+                        }
+                    }
                 }
             }
         }
     }
 
+    private fun updateActions(
+        actions: List<ActionButtonViewModel>,
+        animationState: AnimationState,
+        view: ScreenshotShelfView,
+        layoutInflater: LayoutInflater
+    ) {
+        val actionsContainer: LinearLayout = view.requireViewById(R.id.screenshot_actions)
+        val visibleActions =
+            actions.filter {
+                it.visible &&
+                    (animationState == AnimationState.ENTRANCE_COMPLETE ||
+                        animationState == AnimationState.ENTRANCE_REVEAL ||
+                        it.showDuringEntrance)
+            }
+
+        if (visibleActions.isNotEmpty()) {
+            view.requireViewById<View>(R.id.actions_container_background).visibility = View.VISIBLE
+        }
+
+        // Remove any buttons not in the new list, then do another pass to add
+        // any new actions and update any that are already there.
+        // This assumes that actions can never change order and that each action
+        // ID is unique.
+        val newIds = visibleActions.map { it.id }
+
+        for (child in actionsContainer.children.toList()) {
+            if (child.tag !in newIds) {
+                actionsContainer.removeView(child)
+            }
+        }
+
+        for ((index, action) in visibleActions.withIndex()) {
+            val currentView: View? = actionsContainer.getChildAt(index)
+            if (action.id == currentView?.tag) {
+                // Same ID, update the display
+                buttonViewBinder.bind(currentView, action)
+            } else {
+                // Different ID. Removals have already happened so this must
+                // mean that the new action must be inserted here.
+                val actionButton =
+                    layoutInflater.inflate(R.layout.shelf_action_chip, actionsContainer, false)
+                actionsContainer.addView(actionButton, index)
+                buttonViewBinder.bind(actionButton, action)
+            }
+        }
+    }
+
     private fun setScreenshotBitmap(screenshotPreview: ImageView, bitmap: Bitmap) {
         screenshotPreview.setImageBitmap(bitmap)
         val hasPortraitAspectRatio = bitmap.width < bitmap.height
@@ -153,4 +230,35 @@
         screenshotPreview.layoutParams = params
         screenshotPreview.requestLayout()
     }
+
+    private fun setScrollablePreview(
+        scrollablePreview: ImageView,
+        bitmap: Bitmap?,
+        scrollableRect: Rect
+    ) {
+        if (bitmap == null) {
+            return
+        }
+        val fixedSize = scrollablePreview.resources.getDimensionPixelSize(R.dimen.overlay_x_scale)
+        val inPortrait =
+            scrollablePreview.resources.configuration.orientation ==
+                Configuration.ORIENTATION_PORTRAIT
+        val scale: Float = fixedSize / ((if (inPortrait) bitmap.width else bitmap.height).toFloat())
+        val params = scrollablePreview.layoutParams
+
+        params.width = (scale * scrollableRect.width()).toInt()
+        params.height = (scale * scrollableRect.height()).toInt()
+        val matrix = Matrix()
+        matrix.setScale(scale, scale)
+        matrix.postTranslate(-scrollableRect.left * scale, -scrollableRect.top * scale)
+
+        scrollablePreview.translationX =
+            (scale *
+                if (scrollablePreview.layoutDirection == LayoutDirection.LTR) scrollableRect.left
+                else scrollableRect.right - (scrollablePreview.parent as View).width)
+        scrollablePreview.translationY = scale * scrollableRect.top
+        scrollablePreview.setImageMatrix(matrix)
+        scrollablePreview.setImageBitmap(bitmap)
+        scrollablePreview.setVisibility(View.VISIBLE)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt
index 55a2ad2..42ad326 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt
@@ -19,8 +19,12 @@
 import android.graphics.drawable.Drawable
 
 /** Data describing how an action should be shown to the user. */
-data class ActionButtonAppearance(
+data class ActionButtonAppearance
+@JvmOverloads
+constructor(
     val icon: Drawable?,
     val label: CharSequence?,
     val description: CharSequence,
+    val tint: Boolean = true,
+    val customBackground: Drawable? = null,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
index c5fa8db..364ab76 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
@@ -20,6 +20,7 @@
     val appearance: ActionButtonAppearance,
     val id: Int,
     val visible: Boolean,
+    val showDuringEntrance: Boolean,
     val onClicked: (() -> Unit)?,
 ) {
     companion object {
@@ -29,7 +30,14 @@
 
         fun withNextId(
             appearance: ActionButtonAppearance,
+            showDuringEntrance: Boolean,
             onClicked: (() -> Unit)?
-        ): ActionButtonViewModel = ActionButtonViewModel(appearance, getId(), true, onClicked)
+        ): ActionButtonViewModel =
+            ActionButtonViewModel(appearance, getId(), true, showDuringEntrance, onClicked)
+
+        fun withNextId(
+            appearance: ActionButtonAppearance,
+            onClicked: (() -> Unit)?
+        ): ActionButtonViewModel = withNextId(appearance, showDuringEntrance = true, onClicked)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
index f67ad40..3f99bc4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.screenshot.ui.viewmodel
 
 import android.graphics.Bitmap
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
 import android.util.Log
 import android.view.accessibility.AccessibilityManager
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -25,10 +27,21 @@
 class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager) {
     private val _preview = MutableStateFlow<Bitmap?>(null)
     val preview: StateFlow<Bitmap?> = _preview
+    private val _scrollingScrim = MutableStateFlow<Bitmap?>(null)
+    val scrollingScrim: StateFlow<Bitmap?> = _scrollingScrim
+    private val _badge = MutableStateFlow<Drawable?>(null)
+    val badge: StateFlow<Drawable?> = _badge
     private val _previewAction = MutableStateFlow<(() -> Unit)?>(null)
     val previewAction: StateFlow<(() -> Unit)?> = _previewAction
     private val _actions = MutableStateFlow(emptyList<ActionButtonViewModel>())
     val actions: StateFlow<List<ActionButtonViewModel>> = _actions
+    private val _animationState = MutableStateFlow(AnimationState.NOT_STARTED)
+    val animationState: StateFlow<AnimationState> = _animationState
+
+    private val _isAnimating = MutableStateFlow(false)
+    val isAnimating: StateFlow<Boolean> = _isAnimating
+    private val _scrollableRect = MutableStateFlow<Rect?>(null)
+    val scrollableRect: StateFlow<Rect?> = _scrollableRect
     val showDismissButton: Boolean
         get() = accessibilityManager.isEnabled
 
@@ -36,13 +49,26 @@
         _preview.value = bitmap
     }
 
+    fun setScrollingScrimBitmap(bitmap: Bitmap?) {
+        _scrollingScrim.value = bitmap
+    }
+
+    fun setScreenshotBadge(badge: Drawable?) {
+        _badge.value = badge
+    }
+
     fun setPreviewAction(onClick: () -> Unit) {
         _previewAction.value = onClick
     }
 
-    fun addAction(actionAppearance: ActionButtonAppearance, onClicked: (() -> Unit)): Int {
+    fun addAction(
+        actionAppearance: ActionButtonAppearance,
+        showDuringEntrance: Boolean,
+        onClicked: (() -> Unit)
+    ): Int {
         val actionList = _actions.value.toMutableList()
-        val action = ActionButtonViewModel.withNextId(actionAppearance, onClicked)
+        val action =
+            ActionButtonViewModel.withNextId(actionAppearance, showDuringEntrance, onClicked)
         actionList.add(action)
         _actions.value = actionList
         return action.id
@@ -57,6 +83,7 @@
                     actionList[index].appearance,
                     actionId,
                     visible,
+                    actionList[index].showDuringEntrance,
                     actionList[index].onClicked
                 )
             _actions.value = actionList
@@ -74,6 +101,7 @@
                     appearance,
                     actionId,
                     actionList[index].visible,
+                    actionList[index].showDuringEntrance,
                     actionList[index].onClicked
                 )
             _actions.value = actionList
@@ -92,13 +120,38 @@
         }
     }
 
+    // TODO: this should be handled entirely within the view binder.
+    fun setAnimationState(state: AnimationState) {
+        _animationState.value = state
+    }
+
+    fun setIsAnimating(isAnimating: Boolean) {
+        _isAnimating.value = isAnimating
+    }
+
+    fun setScrollableRect(rect: Rect?) {
+        _scrollableRect.value = rect
+    }
+
     fun reset() {
         _preview.value = null
+        _scrollingScrim.value = null
+        _badge.value = null
         _previewAction.value = null
         _actions.value = listOf()
+        _animationState.value = AnimationState.NOT_STARTED
+        _isAnimating.value = false
+        _scrollableRect.value = null
     }
 
     companion object {
         const val TAG = "ScreenshotViewModel"
     }
 }
+
+enum class AnimationState {
+    NOT_STARTED,
+    ENTRANCE_STARTED, // The first 200ms of the entrance animation
+    ENTRANCE_REVEAL, // The rest of the entrance animation
+    ENTRANCE_COMPLETE,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
index 288ff09..84156eeb 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
@@ -51,6 +51,16 @@
         return super.onTouchEvent(event);
     }
 
+    @Override
+    public boolean onHoverEvent(MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
+            setHovered(true);
+        } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
+            setHovered(false);
+        }
+        return true;
+    }
+
     public void setAccessibilityLabel(String label) {
         mAccessibilityLabel = label;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 851bfca..22aa492 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -40,6 +40,7 @@
 import com.android.systemui.communal.dagger.Communal
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.ui.compose.CommunalContainer
+import com.android.systemui.communal.ui.compose.CommunalContent
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.util.CommunalColors
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -47,12 +48,12 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.statusbar.phone.SystemUIDialogFactory
-import com.android.systemui.util.kotlin.BooleanFlowOperators.and
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
+import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.not
-import com.android.systemui.util.kotlin.BooleanFlowOperators.or
 import com.android.systemui.util.kotlin.collectFlow
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -68,13 +69,12 @@
 constructor(
     private val communalInteractor: CommunalInteractor,
     private val communalViewModel: CommunalViewModel,
-    private val dialogFactory: SystemUIDialogFactory,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val keyguardInteractor: KeyguardInteractor,
     private val shadeInteractor: ShadeInteractor,
     private val powerManager: PowerManager,
     private val communalColors: CommunalColors,
     private val ambientTouchComponentFactory: AmbientTouchComponent.Factory,
+    private val communalContent: CommunalContent,
     @Communal private val dataSourceDelegator: SceneDataSourceDelegator
 ) : LifecycleOwner {
     /** The container view for the hub. This will not be initialized until [initView] is called. */
@@ -102,12 +102,9 @@
     private var rightEdgeSwipeRegionWidth: Int = 0
 
     /**
-     * True if we are currently tracking a gesture for opening the hub that started in the edge
-     * swipe region.
+     * True if we are currently tracking a touch intercepted by the hub, either because the hub is
+     * open or being opened.
      */
-    private var isTrackingOpenGesture = false
-
-    /** True if we are currently tracking a touch on the hub while it's open. */
     private var isTrackingHubTouch = false
 
     /**
@@ -126,15 +123,9 @@
     private var anyBouncerShowing = false
 
     /**
-     * True if the shade is fully expanded and the user is not interacting with it anymore, meaning
-     * the hub should not receive any touch input.
+     * True if the shade is fully expanded, meaning the hub should not receive any touch input.
      *
-     * We need to not pause the touch handling lifecycle as soon as the shade opens because if the
-     * user swipes down, then back up without lifting their finger, the lifecycle will be paused
-     * then resumed, and resuming force-stops all active touch sessions. This means the shade will
-     * not receive the end of the gesture and will be stuck open.
-     *
-     * Based on [ShadeInteractor.isAnyFullyExpanded] and [ShadeInteractor.isUserInteracting].
+     * Tracks [ShadeInteractor.isAnyFullyExpanded].
      */
     private var shadeShowing = false
 
@@ -148,12 +139,12 @@
 
     /** Returns a flow that tracks whether communal hub is available. */
     fun communalAvailable(): Flow<Boolean> =
-        or(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen)
+        anyOf(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen)
 
     /**
      * Creates the container view containing the glanceable hub UI.
      *
-     * @throws RuntimeException if [isEnabled] is false or the view is already initialized
+     * @throws RuntimeException if the view is already initialized
      */
     fun initView(
         context: Context,
@@ -183,7 +174,7 @@
                                         viewModel = communalViewModel,
                                         colors = communalColors,
                                         dataSourceDelegator = dataSourceDelegator,
-                                        dialogFactory = dialogFactory,
+                                        content = communalContent,
                                     )
                                 }
                             }
@@ -197,6 +188,7 @@
     /** Override for testing. */
     @VisibleForTesting
     internal fun initView(containerView: View): View {
+        SceneContainerFlag.assertInLegacyMode()
         if (communalContainerView != null) {
             throw RuntimeException("Communal view has already been initialized")
         }
@@ -227,7 +219,7 @@
 
         // BouncerSwipeTouchHandler has a larger gesture area than we want, set an exclusion area so
         // the gesture area doesn't overlap with widgets.
-        // TODO(b/323035776): adjust gesture areaa for portrait mode
+        // TODO(b/323035776): adjust gesture area for portrait mode
         containerView.repeatWhenAttached {
             // Run when the touch handling lifecycle is RESUMED, meaning the hub is visible and not
             // occluded.
@@ -250,7 +242,7 @@
         // transition to the bouncer would be incorrectly intercepted by the hub.
         collectFlow(
             containerView,
-            or(
+            anyOf(
                 keyguardInteractor.primaryBouncerShowing,
                 keyguardInteractor.alternateBouncerShowing
             ),
@@ -261,7 +253,7 @@
         )
         collectFlow(
             containerView,
-            communalInteractor.isCommunalShowing,
+            communalInteractor.isCommunalVisible,
             {
                 hubShowing = it
                 updateTouchHandlingState()
@@ -269,7 +261,7 @@
         )
         collectFlow(
             containerView,
-            and(shadeInteractor.isAnyFullyExpanded, not(shadeInteractor.isUserInteracting)),
+            allOf(shadeInteractor.isAnyFullyExpanded, not(shadeInteractor.isUserInteracting)),
             {
                 shadeShowing = it
                 updateTouchHandlingState()
@@ -306,6 +298,7 @@
 
     /** Removes the container view from its parent. */
     fun disposeView() {
+        SceneContainerFlag.assertInLegacyMode()
         communalContainerView?.let {
             (it.parent as ViewGroup).removeView(it)
             lifecycleRegistry.currentState = Lifecycle.State.CREATED
@@ -323,20 +316,11 @@
      * to be fully in control of its own touch handling.
      */
     fun onTouchEvent(ev: MotionEvent): Boolean {
+        SceneContainerFlag.assertInLegacyMode()
         return communalContainerView?.let { handleTouchEventOnCommunalView(it, ev) } ?: false
     }
 
     private fun handleTouchEventOnCommunalView(view: View, ev: MotionEvent): Boolean {
-        // If the hub is fully visible, send all touch events to it, other than top and bottom edge
-        // swipes.
-        return if (hubShowing) {
-            handleHubOpenTouch(view, ev)
-        } else {
-            handleHubClosedTouch(view, ev)
-        }
-    }
-
-    private fun handleHubOpenTouch(view: View, ev: MotionEvent): Boolean {
         val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN
         val isUp = ev.actionMasked == MotionEvent.ACTION_UP
         val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL
@@ -344,50 +328,18 @@
         val hubOccluded = anyBouncerShowing || shadeShowing
 
         if (isDown && !hubOccluded) {
-            // Only intercept down events if the hub isn't occluded by the bouncer or
-            // notification shade.
-            isTrackingHubTouch = true
-        }
-
-        if (isTrackingHubTouch) {
-            // Tracking a touch on the hub UI itself.
-            if (isUp || isCancel) {
-                isTrackingHubTouch = false
-            }
-            dispatchTouchEvent(view, ev)
-            // Return true regardless of dispatch result as some touches at the start of a
-            // gesture
-            // may return false from dispatchTouchEvent.
-            return true
-        }
-
-        return false
-    }
-
-    private fun handleHubClosedTouch(view: View, ev: MotionEvent): Boolean {
-        val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN
-        val isUp = ev.actionMasked == MotionEvent.ACTION_UP
-        val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL
-
-        val hubOccluded = anyBouncerShowing || shadeShowing
-
-        if (rightEdgeSwipeRegionWidth == 0) {
-            // If the edge region width has not been read yet for whatever reason, don't bother
-            // intercepting touches to open the hub.
-            return false
-        }
-
-        if (isDown && !hubOccluded) {
             val x = ev.rawX
             val inOpeningSwipeRegion: Boolean = x >= view.width - rightEdgeSwipeRegionWidth
-            if (inOpeningSwipeRegion) {
-                isTrackingOpenGesture = true
+            if (inOpeningSwipeRegion || hubShowing) {
+                // Steal touch events when the hub is open, or if the touch started in the opening
+                // gesture region.
+                isTrackingHubTouch = true
             }
         }
 
-        if (isTrackingOpenGesture) {
+        if (isTrackingHubTouch) {
             if (isUp || isCancel) {
-                isTrackingOpenGesture = false
+                isTrackingHubTouch = false
             }
             dispatchTouchEvent(view, ev)
             // Return true regardless of dispatch result as some touches at the start of a gesture
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 9f1b423..6bb30c7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -141,6 +141,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver;
 import com.android.systemui.keyguard.shared.ComposeLockscreen;
+import com.android.systemui.keyguard.shared.model.Edge;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder;
@@ -170,6 +171,7 @@
 import com.android.systemui.power.shared.model.WakefulnessModel;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.shared.model.Scenes;
 import com.android.systemui.shade.data.repository.FlingInfo;
 import com.android.systemui.shade.data.repository.ShadeRepository;
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
@@ -620,6 +622,7 @@
     private int mDreamingToLockscreenTransitionTranslationY;
     private int mLockscreenToDreamingTransitionTranslationY;
     private int mGoneToDreamingTransitionTranslationY;
+    private boolean mForceFlingAnimationForTest = false;
     private final SplitShadeStateController mSplitShadeStateController;
     private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */,
             mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */);
@@ -1129,8 +1132,12 @@
                 controller.setup(mNotificationContainerParent));
 
         // Dreaming->Lockscreen
-        collectFlow(mView, mKeyguardTransitionInteractor.transition(DREAMING, LOCKSCREEN),
-                mDreamingToLockscreenTransition, mMainDispatcher);
+        collectFlow(
+                mView,
+                mKeyguardTransitionInteractor.transition(
+                        Edge.Companion.create(DREAMING, LOCKSCREEN)),
+                mDreamingToLockscreenTransition,
+                mMainDispatcher);
         collectFlow(mView, mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha(),
                 setDreamLockscreenTransitionAlpha(mNotificationStackScrollLayoutController),
                 mMainDispatcher);
@@ -1140,7 +1147,8 @@
 
         // Gone -> Dreaming hosted in lockscreen
         collectFlow(mView, mKeyguardTransitionInteractor
-                        .transition(GONE, DREAMING_LOCKSCREEN_HOSTED),
+                        .transition(Edge.Companion.create(Scenes.Gone, DREAMING_LOCKSCREEN_HOSTED),
+                                Edge.Companion.create(GONE, DREAMING_LOCKSCREEN_HOSTED)),
                 mGoneToDreamingLockscreenHostedTransition, mMainDispatcher);
         collectFlow(mView, mGoneToDreamingLockscreenHostedTransitionViewModel.getLockscreenAlpha(),
                 setTransitionAlpha(mNotificationStackScrollLayoutController),
@@ -1148,16 +1156,17 @@
 
         // Lockscreen -> Dreaming hosted in lockscreen
         collectFlow(mView, mKeyguardTransitionInteractor
-                        .transition(LOCKSCREEN, DREAMING_LOCKSCREEN_HOSTED),
+                        .transition(Edge.Companion.create(LOCKSCREEN, DREAMING_LOCKSCREEN_HOSTED)),
                 mLockscreenToDreamingLockscreenHostedTransition, mMainDispatcher);
 
         // Dreaming hosted in lockscreen -> Lockscreen
         collectFlow(mView, mKeyguardTransitionInteractor
-                        .transition(DREAMING_LOCKSCREEN_HOSTED, LOCKSCREEN),
+                        .transition(Edge.Companion.create(DREAMING_LOCKSCREEN_HOSTED, LOCKSCREEN)),
                 mDreamingLockscreenHostedToLockscreenTransition, mMainDispatcher);
 
         // Occluded->Lockscreen
-        collectFlow(mView, mKeyguardTransitionInteractor.transition(OCCLUDED, LOCKSCREEN),
+        collectFlow(mView, mKeyguardTransitionInteractor.transition(
+                Edge.Companion.create(OCCLUDED, LOCKSCREEN)),
                 mOccludedToLockscreenTransition, mMainDispatcher);
         if (!MigrateClocksToBlueprint.isEnabled()) {
             collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha(),
@@ -1168,7 +1177,8 @@
         }
 
         // Lockscreen->Dreaming
-        collectFlow(mView, mKeyguardTransitionInteractor.transition(LOCKSCREEN, DREAMING),
+        collectFlow(mView, mKeyguardTransitionInteractor.transition(
+                Edge.Companion.create(LOCKSCREEN, DREAMING)),
                 mLockscreenToDreamingTransition, mMainDispatcher);
         if (!MigrateClocksToBlueprint.isEnabled()) {
             collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(),
@@ -1180,7 +1190,9 @@
                 setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
 
         // Gone->Dreaming
-        collectFlow(mView, mKeyguardTransitionInteractor.transition(GONE, DREAMING),
+        collectFlow(mView, mKeyguardTransitionInteractor.transition(
+                Edge.Companion.create(Scenes.Gone, DREAMING),
+                        Edge.Companion.create(GONE, DREAMING)),
                 mGoneToDreamingTransition, mMainDispatcher);
         if (!MigrateClocksToBlueprint.isEnabled()) {
             collectFlow(mView, mGoneToDreamingTransitionViewModel.getLockscreenAlpha(),
@@ -1191,7 +1203,8 @@
                 setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
 
         // Lockscreen->Occluded
-        collectFlow(mView, mKeyguardTransitionInteractor.transition(LOCKSCREEN, OCCLUDED),
+        collectFlow(mView, mKeyguardTransitionInteractor.transition(
+                Edge.Companion.create(LOCKSCREEN, OCCLUDED)),
                 mLockscreenToOccludedTransition, mMainDispatcher);
         if (!MigrateClocksToBlueprint.isEnabled()) {
             collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha(),
@@ -2218,11 +2231,19 @@
                 }
             }
         });
+        if (!mScrimController.isScreenOn() && !mForceFlingAnimationForTest) {
+            animator.setDuration(1);
+        }
         setAnimator(animator);
         animator.start();
     }
 
     @VisibleForTesting
+    void setForceFlingAnimationForTest(boolean force) {
+        mForceFlingAnimationForTest = force;
+    }
+
+    @VisibleForTesting
     void onFlingEnd(boolean cancelled) {
         mIsFlinging = false;
         // No overshoot when the animation ends
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 44f86da..6efa633 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -49,9 +49,11 @@
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.MigrateClocksToBlueprint;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.Edge;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
 import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
 import com.android.systemui.statusbar.DragDownHelper;
@@ -136,11 +138,6 @@
     private final PanelExpansionInteractor mPanelExpansionInteractor;
     private final ShadeExpansionStateManager mShadeExpansionStateManager;
 
-    /**
-     * If {@code true}, an external touch sent in {@link #handleExternalTouch(MotionEvent)} has been
-     * intercepted and all future touch events for the gesture should be processed by this view.
-     */
-    private boolean mExternalTouchIntercepted = false;
     private boolean mIsTrackingBarGesture = false;
     private boolean mIsOcclusionTransitionRunning = false;
     private DisableSubpixelTextTransitionListener mDisableSubpixelTextTransitionListener;
@@ -224,7 +221,8 @@
         mDisableSubpixelTextTransitionListener = new DisableSubpixelTextTransitionListener(mView);
         bouncerViewBinder.bind(mView.findViewById(R.id.keyguard_bouncer_container));
 
-        collectFlow(mView, keyguardTransitionInteractor.transition(LOCKSCREEN, DREAMING),
+        collectFlow(mView, keyguardTransitionInteractor.transition(
+                Edge.Companion.create(LOCKSCREEN, DREAMING)),
                 mLockscreenToDreamingTransition);
         collectFlow(
                 mView,
@@ -257,28 +255,11 @@
     }
 
     /**
-     * Handle a touch event while dreaming or on the hub by forwarding the event to the content
-     * view.
-     * <p>
-     * Since important logic for handling touches lives in the dispatch/intercept phases, we
-     * simulate going through all of these stages before sending onTouchEvent if intercepted.
-     *
+     * Handle a touch event while dreaming by forwarding the event to the content view.
      * @param event The event to forward.
      */
-    public void handleExternalTouch(MotionEvent event) {
-        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-            mExternalTouchIntercepted = false;
-        }
-
-        if (!mView.dispatchTouchEvent(event)) {
-            return;
-        }
-        if (!mExternalTouchIntercepted) {
-            mExternalTouchIntercepted = mView.onInterceptTouchEvent(event);
-        }
-        if (mExternalTouchIntercepted) {
-            mView.onTouchEvent(event);
-        }
+    public void handleDreamTouch(MotionEvent event) {
+        mView.dispatchTouchEvent(event);
     }
 
     /** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */
@@ -357,7 +338,9 @@
                 mFalsingCollector.onTouchEvent(ev);
                 mPulsingWakeupGestureHandler.onTouchEvent(ev);
 
-                if (mGlanceableHubContainerController.onTouchEvent(ev)) {
+                if (!SceneContainerFlag.isEnabled()
+                        && mGlanceableHubContainerController.onTouchEvent(ev)) {
+                    // GlanceableHubContainerController is only used pre-flexiglass.
                     return logDownDispatch(ev, "dispatched to glanceable hub container", true);
                 }
                 if (mDreamingWakeupGestureHandler != null
@@ -621,6 +604,10 @@
      * The layout lives in {@link R.id.communal_ui_stub}.
      */
     public void setupCommunalHubLayout() {
+        if (SceneContainerFlag.isEnabled()) {
+            // GlanceableHubContainerController is only used pre-flexiglass.
+            return;
+        }
         collectFlow(
                 mView,
                 mGlanceableHubContainerController.communalAvailable(),
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index 43e782c..884ccef 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -28,7 +28,6 @@
 import com.android.systemui.log.dagger.ShadeTouchLog
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.shared.model.TransitionKeys.CollapseShadeInstantly
 import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse
 import com.android.systemui.shade.ShadeController.ShadeVisibilityListener
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -100,11 +99,9 @@
     }
 
     override fun instantCollapseShade() {
-        // TODO(b/325602936) add support for instant transition
-        sceneInteractor.changeScene(
+        sceneInteractor.snapToScene(
             getCollapseDestinationScene(),
             "hide shade",
-            CollapseShadeInstantly,
         )
     }
 
@@ -143,7 +140,7 @@
 
     private fun animateCollapseShadeInternal() {
         sceneInteractor.changeScene(
-            getCollapseDestinationScene(),
+            getCollapseDestinationScene(), // TODO(b/336581871): add sceneState?
             "ShadeController.animateCollapseShade",
             SlightlyFasterShadeCollapse,
         )
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 c9949cd..55bd8c6 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
@@ -44,6 +44,7 @@
                 } else {
                     Scenes.Shade
                 }
+            // TODO(b/336581871): add sceneState?
             sceneInteractor.changeScene(key, "animateCollapseQs")
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index ac76bec..e4a2424 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -27,12 +27,13 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
 import com.android.systemui.qs.FooterActionsController
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
 import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
@@ -60,7 +61,7 @@
     val shadeHeaderViewModel: ShadeHeaderViewModel,
     val notifications: NotificationsPlaceholderViewModel,
     val brightnessMirrorViewModel: BrightnessMirrorViewModel,
-    val mediaDataManager: MediaDataManager,
+    val mediaCarouselInteractor: MediaCarouselInteractor,
     shadeInteractor: ShadeInteractor,
     private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
     private val footerActionsController: FooterActionsController,
@@ -108,6 +109,8 @@
 
     val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
 
+    val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasActiveMediaOrRecommendation
+
     /**
      * Amount of X-axis translation to apply to various elements as the unfolded foldable is folded
      * slightly, in pixels.
@@ -125,11 +128,6 @@
         sceneInteractor.changeScene(Scenes.Lockscreen, "Shade empty content clicked")
     }
 
-    fun isMediaVisible(): Boolean {
-        // TODO(b/296122467): handle updates to carousel visibility while scene is still visible
-        return mediaDataManager.hasActiveMediaOrRecommendation()
-    }
-
     private val footerActionsControllerInitialized = AtomicBoolean(false)
 
     fun getFooterActionsViewModel(lifecycleOwner: LifecycleOwner): FooterActionsViewModel {
@@ -152,11 +150,13 @@
                 else -> Scenes.Lockscreen
             }
 
+        val upTransitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+
         val down = Scenes.QuickSettings.takeIf { shadeMode is ShadeMode.Single }
 
         return buildMap {
             if (!isCustomizing) {
-                this[Swipe(SwipeDirection.Up)] = UserActionResult(up)
+                this[Swipe(SwipeDirection.Up)] = UserActionResult(up, upTransitionKey)
             } // TODO(b/330200163) Add an else to be able to collapse the shade while customizing
             down?.let { this[Swipe(SwipeDirection.Down)] = UserActionResult(down) }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt b/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt
index 384acc4..dd79425 100644
--- a/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt
@@ -28,6 +28,9 @@
  * Returns updating [Slice] for a [sliceUri]. It's null when there is no slice available for the
  * provided Uri. This can change overtime because of external changes (like device being
  * connected/disconnected).
+ *
+ * The flow should be [kotlinx.coroutines.flow.flowOn] the main thread because [SliceViewManager]
+ * isn't thread-safe. An exception will be thrown otherwise.
  */
 fun SliceViewManager.sliceForUri(sliceUri: Uri): Flow<Slice?> =
     ConflatedCallbackFlow.conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index d955349..c912616 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -134,7 +134,7 @@
     private static final int MSG_DISMISS_KEYBOARD_SHORTCUTS        = 32 << MSG_SHIFT;
     private static final int MSG_HANDLE_SYSTEM_KEY                 = 33 << MSG_SHIFT;
     private static final int MSG_SHOW_GLOBAL_ACTIONS               = 34 << MSG_SHIFT;
-    private static final int MSG_TOGGLE_PANEL                      = 35 << MSG_SHIFT;
+    private static final int MSG_TOGGLE_NOTIFICATION_PANEL         = 35 << MSG_SHIFT;
     private static final int MSG_SHOW_SHUTDOWN_UI                  = 36 << MSG_SHIFT;
     private static final int MSG_SET_TOP_APP_HIDES_STATUS_BAR      = 37 << MSG_SHIFT;
     private static final int MSG_ROTATION_PROPOSAL                 = 38 << MSG_SHIFT;
@@ -180,6 +180,7 @@
     private static final int MSG_SET_QS_TILES = 79 << MSG_SHIFT;
     private static final int MSG_ENTER_DESKTOP = 80 << MSG_SHIFT;
     private static final int MSG_SET_SPLITSCREEN_FOCUS = 81 << MSG_SHIFT;
+    private static final int MSG_TOGGLE_QUICK_SETTINGS_PANEL = 82 << MSG_SHIFT;
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
     public static final int FLAG_EXCLUDE_RECENTS_PANEL = 1 << 1;
@@ -222,10 +223,33 @@
          */
         default void disable(int displayId, @DisableFlags int state1, @Disable2Flags int state2,
                 boolean animate) { }
+
+        /**
+         * Called to expand Notifications panel with animation.
+         */
         default void animateExpandNotificationsPanel() { }
+        /**
+         * Called to collapse Notifications panel with animation.
+         * @param flags Exclusion flags. See {@link FLAG_EXCLUDE_NONE}.
+         * @param force True to force the operation.
+         */
         default void animateCollapsePanels(int flags, boolean force) { }
-        default void togglePanel() { }
-        default void animateExpandSettingsPanel(String obj) { }
+
+        /**
+         * Called to toggle Notifications panel.
+         */
+        default void toggleNotificationsPanel() { }
+
+        /**
+         * Called to expand Quick Settings panel with animation.
+         * @param subPanel subPanel one wish to expand.
+         */
+        default void animateExpandSettingsPanel(String subPanel) { }
+
+        /**
+         * Called to toggle Quick Settings panel.
+         */
+        default void toggleQuickSettingsPanel() { }
 
         /**
          * Called to notify IME window status changes.
@@ -696,10 +720,10 @@
         }
     }
 
-    public void togglePanel() {
+    public void toggleNotificationsPanel() {
         synchronized (mLock) {
-            mHandler.removeMessages(MSG_TOGGLE_PANEL);
-            mHandler.obtainMessage(MSG_TOGGLE_PANEL, 0, 0).sendToTarget();
+            mHandler.removeMessages(MSG_TOGGLE_NOTIFICATION_PANEL);
+            mHandler.obtainMessage(MSG_TOGGLE_NOTIFICATION_PANEL, 0, 0).sendToTarget();
         }
     }
 
@@ -710,6 +734,13 @@
         }
     }
 
+    public void toggleQuickSettingsPanel() {
+        synchronized (mLock) {
+            mHandler.removeMessages(MSG_TOGGLE_QUICK_SETTINGS_PANEL);
+            mHandler.obtainMessage(MSG_TOGGLE_QUICK_SETTINGS_PANEL, 0, 0).sendToTarget();
+        }
+    }
+
     @Override
     public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
             boolean showImeSwitcher) {
@@ -1494,9 +1525,9 @@
                         mCallbacks.get(i).animateCollapsePanels(msg.arg1, msg.arg2 != 0);
                     }
                     break;
-                case MSG_TOGGLE_PANEL:
+                case MSG_TOGGLE_NOTIFICATION_PANEL:
                     for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).togglePanel();
+                        mCallbacks.get(i).toggleNotificationsPanel();
                     }
                     break;
                 case MSG_EXPAND_SETTINGS:
@@ -1504,6 +1535,11 @@
                         mCallbacks.get(i).animateExpandSettingsPanel((String) msg.obj);
                     }
                     break;
+                case MSG_TOGGLE_QUICK_SETTINGS_PANEL:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).toggleQuickSettingsPanel();
+                    }
+                    break;
                 case MSG_SHOW_IME_BUTTON:
                     args = (SomeArgs) msg.obj;
                     handleShowImeButton(args.argi1 /* displayId */, (IBinder) args.arg1 /* token */,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 7983db1..2446473 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -199,12 +199,14 @@
     protected boolean mPowerPluggedInWired;
     protected boolean mPowerPluggedInWireless;
     protected boolean mPowerPluggedInDock;
+    protected int mChargingSpeed;
 
     private boolean mPowerCharged;
+    /** Whether the battery defender is triggered. */
     private boolean mBatteryDefender;
+    /** Whether the battery defender is triggered with the device plugged. */
     private boolean mEnableBatteryDefender;
     private boolean mIncompatibleCharger;
-    protected int mChargingSpeed;
     private int mChargingWattage;
     private int mBatteryLevel;
     private boolean mBatteryPresent = true;
@@ -1244,7 +1246,7 @@
             mChargingSpeed = status.getChargingSpeed(mContext);
             mBatteryLevel = status.level;
             mBatteryPresent = status.present;
-            mBatteryDefender = status.isBatteryDefender();
+            mBatteryDefender = isBatteryDefender(status);
             // when the battery is overheated, device doesn't charge so only guard on pluggedIn:
             mEnableBatteryDefender = mBatteryDefender && status.isPluggedIn();
             mIncompatibleCharger = status.incompatibleCharger.orElse(false);
@@ -1516,6 +1518,11 @@
         return mPowerPluggedIn;
     }
 
+    /** Return true if the device is under the battery defender mode. */
+    protected boolean isBatteryDefender(BatteryStatus status) {
+        return status.isBatteryDefender();
+    }
+
     private boolean isCurrentUser(int userId) {
         return getCurrentUser() == userId;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 222b070..14e14f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -14,7 +14,6 @@
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.Dumpable
 import com.android.systemui.ExpandHelper
-import com.android.systemui.Flags.nsslFalsingFix
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy
 import com.android.systemui.classifier.Classifier
@@ -884,9 +883,7 @@
                     isDraggingDown = false
                     isTrackpadReverseScroll = false
                     shadeRepository.setLegacyLockscreenShadeTracking(false)
-                    if (nsslFalsingFix() || MigrateClocksToBlueprint.isEnabled) {
-                        return true
-                    }
+                    return true
                 } else {
                     stopDragging()
                     return false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index d7d3732..5bf2f41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -15,6 +15,8 @@
  */
 package com.android.systemui.statusbar;
 
+import static com.android.systemui.Flags.mediaControlsUserInitiatedDismiss;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Notification;
@@ -175,14 +177,18 @@
             }
 
             @Override
-            public void onMediaDataRemoved(@NonNull String key) {
+            public void onMediaDataRemoved(@NonNull String key, boolean userInitiated) {
+                if (mediaControlsUserInitiatedDismiss() && !userInitiated) {
+                    // Dismissing the notification will send the app's deleteIntent, so ignore if
+                    // this was an automatic removal
+                    Log.d(TAG, "Not dismissing " + key + " because it was removed by the system");
+                    return;
+                }
                 mNotifPipeline.getAllNotifs()
                         .stream()
                         .filter(entry -> Objects.equals(entry.getKey(), key))
                         .findAny()
                         .ifPresent(entry -> {
-                            // TODO(b/160713608): "removing" this notification won't happen and
-                            //  won't send the 'deleteIntent' if the notification is ongoing.
                             mNotifCollection.dismissNotification(entry,
                                     getDismissedByUserStats(entry));
                         });
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 70632d5..79218ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_TO_AOD;
+import static com.android.systemui.keyguard.shared.model.KeyguardState.GONE;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
 
 import android.animation.Animator;
@@ -49,6 +50,7 @@
 import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus;
 import com.android.systemui.keyguard.MigrateClocksToBlueprint;
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
@@ -108,6 +110,7 @@
     private final UiEventLogger mUiEventLogger;
     private final Lazy<InteractionJankMonitor> mInteractionJankMonitorLazy;
     private final JavaAdapter mJavaAdapter;
+    private final Lazy<KeyguardTransitionInteractor> mKeyguardTransitionInteractorLazy;
     private final Lazy<ShadeInteractor> mShadeInteractorLazy;
     private final Lazy<DeviceUnlockedInteractor> mDeviceUnlockedInteractorLazy;
     private final Lazy<SceneInteractor> mSceneInteractorLazy;
@@ -175,6 +178,7 @@
             UiEventLogger uiEventLogger,
             Lazy<InteractionJankMonitor> interactionJankMonitorLazy,
             JavaAdapter javaAdapter,
+            Lazy<KeyguardTransitionInteractor> keyguardTransitionInteractor,
             Lazy<ShadeInteractor> shadeInteractorLazy,
             Lazy<DeviceUnlockedInteractor> deviceUnlockedInteractorLazy,
             Lazy<SceneInteractor> sceneInteractorLazy,
@@ -182,6 +186,7 @@
         mUiEventLogger = uiEventLogger;
         mInteractionJankMonitorLazy = interactionJankMonitorLazy;
         mJavaAdapter = javaAdapter;
+        mKeyguardTransitionInteractorLazy = keyguardTransitionInteractor;
         mShadeInteractorLazy = shadeInteractorLazy;
         mDeviceUnlockedInteractorLazy = deviceUnlockedInteractorLazy;
         mSceneInteractorLazy = sceneInteractorLazy;
@@ -193,6 +198,14 @@
 
     @Override
     public void start() {
+        mJavaAdapter.alwaysCollectFlow(
+                mKeyguardTransitionInteractorLazy.get().isFinishedInState(GONE),
+                (Boolean isFinishedInState) -> {
+                    if (isFinishedInState) {
+                        setLeaveOpenOnKeyguardHide(false);
+                    }
+                });
+
         mJavaAdapter.alwaysCollectFlow(mShadeInteractorLazy.get().isAnyExpanded(),
                 this::onShadeOrQsExpanded);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainer.kt
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainer.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainer.kt
index ce88a5f..cae86a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainer.kt
@@ -14,18 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone.ongoingcall
+package com.android.systemui.statusbar.chips.ui.view
 
 import android.content.Context
 import android.util.AttributeSet
 import com.android.systemui.animation.view.LaunchableLinearLayout
 
 /**
- * A container view for the ongoing call chip background. Needed so that we can limit the height of
- * the background when the font size is very large (200%), in which case the background would go
+ * A container view for the ongoing activity chip background. Needed so that we can limit the height
+ * of the background when the font size is very large (200%), in which case the background would go
  * past the bounds of the status bar.
  */
-class OngoingCallBackgroundContainer(context: Context, attrs: AttributeSet) :
+class ChipBackgroundContainer(context: Context, attrs: AttributeSet) :
     LaunchableLinearLayout(context, attrs) {
 
     /** Sets where this view should fetch its max height from. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt
index bb7ba4c..ff3061e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt
@@ -14,36 +14,34 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone.ongoingcall
+package com.android.systemui.statusbar.chips.ui.view
 
 import android.content.Context
 import android.util.AttributeSet
-
 import android.widget.Chronometer
 import androidx.annotation.UiThread
 
 /**
- * A [Chronometer] specifically for the ongoing call chip in the status bar.
+ * A [Chronometer] specifically for chips in the status bar that show ongoing duration of an
+ * activity.
  *
  * This class handles:
- *   1) Setting the text width. If we used a basic WRAP_CONTENT for width, the chip width would
- *      change slightly each second because the width of each number is slightly different.
+ * 1) Setting the text width. If we used a basic WRAP_CONTENT for width, the chip width would change
+ *    slightly each second because the width of each number is slightly different.
  *
- *      Instead, we save the largest number width seen so far and ensure that the chip is at least
- *      that wide. This means the chip may get larger over time (e.g. in the transition from 59:59
- *      to 1:00:00), but never smaller.
- *
- *   2) Hiding the text if the time gets too long for the space available. Once the text has been
- *      hidden, it remains hidden for the duration of the call.
+ *    Instead, we save the largest number width seen so far and ensure that the chip is at least
+ *    that wide. This means the chip may get larger over time (e.g. in the transition from 59:59 to
+ *    1:00:00), but never smaller.
+ * 2) Hiding the text if the time gets too long for the space available. Once the text has been
+ *    hidden, it remains hidden for the duration of the activity.
  *
  * Note that if the text was too big in portrait mode, resulting in the text being hidden, then the
  * text will also be hidden in landscape (even if there is enough space for it in landscape).
  */
-class OngoingCallChronometer @JvmOverloads constructor(
-    context: Context,
-    attrs: AttributeSet? = null,
-    defStyle: Int = 0
-) : Chronometer(context, attrs, defStyle) {
+class ChipChronometer
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
+    Chronometer(context, attrs, defStyle) {
 
     // Minimum width that the text view can be. Corresponds with the largest number width seen so
     // far.
@@ -53,8 +51,8 @@
     private var shouldHideText: Boolean = false
 
     override fun setBase(base: Long) {
-        // These variables may have changed during the previous call, so re-set them before the new
-        // call starts.
+        // These variables may have changed during the previous activity, so re-set them before the
+        // new activity starts.
         minimumTextWidth = 0
         shouldHideText = false
         visibility = VISIBLE
@@ -75,9 +73,7 @@
         }
 
         // Evaluate how wide the text *wants* to be if it had unlimited space.
-        super.onMeasure(
-                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
-                heightMeasureSpec)
+        super.onMeasure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), heightMeasureSpec)
         val desiredTextWidth = measuredWidth
 
         // Evaluate how wide the text *can* be based on the enforced constraints
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 c17da4b..0524589 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -32,6 +32,7 @@
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpHandler;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
@@ -56,10 +57,10 @@
 import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
-import com.android.systemui.statusbar.phone.ui.StatusBarIconList;
 import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
 import com.android.systemui.statusbar.phone.ui.StatusBarIconControllerImpl;
+import com.android.systemui.statusbar.phone.ui.StatusBarIconList;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import dagger.Binds;
@@ -209,14 +210,16 @@
     /** */
     @Provides
     @SysUISingleton
-    static ActivityTransitionAnimator provideActivityTransitionAnimator() {
-        return new ActivityTransitionAnimator();
+    static ActivityTransitionAnimator provideActivityTransitionAnimator(
+            @Main Executor mainExecutor) {
+        return new ActivityTransitionAnimator(mainExecutor);
     }
 
     /** */
     @Provides
     @SysUISingleton
-    static DialogTransitionAnimator provideDialogTransitionAnimator(IDreamManager dreamManager,
+    static DialogTransitionAnimator provideDialogTransitionAnimator(@Main Executor mainExecutor,
+            IDreamManager dreamManager,
             KeyguardStateController keyguardStateController,
             Lazy<AlternateBouncerInteractor> alternateBouncerInteractor,
             InteractionJankMonitor interactionJankMonitor,
@@ -243,7 +246,7 @@
             }
         };
         return new DialogTransitionAnimator(
-                callback, interactionJankMonitor, animationFeatureFlags);
+                mainExecutor, callback, interactionJankMonitor, animationFeatureFlags);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 446a0d7..455c964 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -310,11 +310,9 @@
 
     fun isWeatherEnabled(): Boolean {
        execution.assertIsMainThread()
-       val defaultValue = context.getResources().getBoolean(
-               com.android.internal.R.bool.config_lockscreenWeatherEnabledByDefault)
        val showWeather = secureSettings.getIntForUser(
            LOCK_SCREEN_WEATHER_ENABLED,
-           if (defaultValue) 1 else 0,
+           1,
            userTracker.userId) == 1
        return showWeather
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java
index 0c341cc..ec3c7d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java
@@ -27,6 +27,9 @@
  * (e.g. clicking on a notification, tapping on the settings icon in the notification guts)
  */
 public interface NotificationActivityStarter {
+    /** Called when the user clicks on the notification bubble icon. */
+    void onNotificationBubbleIconClicked(NotificationEntry entry);
+
     /** Called when the user clicks on the surface of a notification. */
     void onNotificationClicked(NotificationEntry entry, ExpandableNotificationRow row);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index d10fac6..6487d55 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -117,11 +117,14 @@
         Notification notification = sbn.getNotification();
         if (notification.contentIntent != null || notification.fullScreenIntent != null
                 || row.getEntry().isBubble()) {
+            row.setBubbleClickListener(v ->
+                    mNotificationActivityStarter.onNotificationBubbleIconClicked(row.getEntry()));
             row.setOnClickListener(this);
             row.setOnDragSuccessListener(mOnDragSuccessListener);
         } else {
             row.setOnClickListener(null);
             row.setOnDragSuccessListener(null);
+            row.setBubbleClickListener(null);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
index 3d0fd89..af2c197 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
@@ -31,8 +31,10 @@
 import com.android.systemui.statusbar.notification.icon.ConversationIconManager
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
 import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
+import com.android.systemui.statusbar.notification.stack.BUCKET_PRIORITY_PEOPLE
 import javax.inject.Inject
 
 /**
@@ -81,6 +83,13 @@
         }
     }
 
+    val priorityPeopleSectioner =
+            object : NotifSectioner("Priority People", BUCKET_PRIORITY_PEOPLE) {
+                override fun isInSection(entry: ListEntry): Boolean {
+                    return getPeopleType(entry) == TYPE_IMPORTANT_PERSON
+                }
+            }
+
     // TODO(b/330193582): Rename to just "People"
     val peopleAlertingSectioner = object : NotifSectioner("People(alerting)", BUCKET_PEOPLE) {
         override fun isInSection(entry: ListEntry): Boolean  {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 1a223c1..42bf4e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -18,12 +18,10 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator
 
-import android.os.SystemProperties
 import android.os.UserHandle
 import android.provider.Settings
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.Dumpable
-import com.android.systemui.Flags.notificationMinimalismPrototype
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dump.DumpManager
@@ -33,14 +31,20 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.expansionChanges
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
 import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.headsUpEvents
 import com.android.systemui.util.asIndenting
@@ -107,6 +111,10 @@
     }
 
     private fun attachUnseenFilter(pipeline: NotifPipeline) {
+        if (NotificationMinimalismPrototype.V2.isEnabled) {
+            pipeline.addPromoter(unseenNotifPromoter)
+            pipeline.addOnBeforeTransformGroupsListener(::pickOutTopUnseenNotif)
+        }
         pipeline.addFinalizeFilter(unseenNotifFilter)
         pipeline.addCollectionListener(collectionListener)
         scope.launch { trackUnseenFilterSettingChanges() }
@@ -264,7 +272,10 @@
     }
 
     private fun unseenFeatureEnabled(): Flow<Boolean> {
-        if (notificationMinimalismPrototype()) {
+        if (
+            NotificationMinimalismPrototype.V1.isEnabled ||
+                NotificationMinimalismPrototype.V2.isEnabled
+        ) {
             return flowOf(true)
         }
         return secureSettings
@@ -335,6 +346,57 @@
             }
         }
 
+    private fun pickOutTopUnseenNotif(list: List<ListEntry>) {
+        if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return
+        // Only ever elevate a top unseen notification on keyguard, not even locked shade
+        if (statusBarStateController.state != StatusBarState.KEYGUARD) {
+            seenNotificationsInteractor.setTopUnseenNotification(null)
+            return
+        }
+        // On keyguard pick the top-ranked unseen or ongoing notification to elevate
+        seenNotificationsInteractor.setTopUnseenNotification(
+            list
+                .asSequence()
+                .flatMap {
+                    when (it) {
+                        is NotificationEntry -> listOfNotNull(it)
+                        is GroupEntry -> it.children
+                        else -> error("unhandled type of $it")
+                    }
+                }
+                .filter { shouldIgnoreUnseenCheck(it) || it in unseenNotifications }
+                .minByOrNull { it.ranking.rank }
+        )
+    }
+
+    @VisibleForTesting
+    internal val unseenNotifPromoter =
+        object : NotifPromoter("$TAG-unseen") {
+            override fun shouldPromoteToTopLevel(child: NotificationEntry): Boolean =
+                if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) false
+                else
+                    seenNotificationsInteractor.isTopUnseenNotification(child) &&
+                        NotificationMinimalismPrototype.V2.ungroupTopUnseen
+        }
+
+    val unseenNotifSectioner =
+        object : NotifSectioner("Unseen", BUCKET_FOREGROUND_SERVICE) {
+            override fun isInSection(entry: ListEntry): Boolean {
+                if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return false
+                if (
+                    seenNotificationsInteractor.isTopUnseenNotification(entry.representativeEntry)
+                ) {
+                    return true
+                }
+                if (entry !is GroupEntry) {
+                    return false
+                }
+                return entry.children.any {
+                    seenNotificationsInteractor.isTopUnseenNotification(it)
+                }
+            }
+        }
+
     @VisibleForTesting
     internal val unseenNotifFilter =
         object : NotifFilter("$TAG-unseen") {
@@ -342,18 +404,6 @@
             var hasFilteredAnyNotifs = false
 
             /**
-             * the [notificationMinimalismPrototype] will now show seen notifications on the locked
-             * shade by default, but this property read allows that to be quickly disabled for
-             * testing
-             */
-            private val minimalismShowOnLockedShade
-                get() =
-                    SystemProperties.getBoolean(
-                        "persist.notification_minimalism_prototype.show_on_locked_shade",
-                        true
-                    )
-
-            /**
              * Encapsulates a definition of "being on the keyguard". Note that these two definitions
              * are wildly different: [StatusBarState.KEYGUARD] is when on the lock screen and does
              * not include shade or occluded states, whereas [KeyguardRepository.isKeyguardShowing]
@@ -364,7 +414,12 @@
              * allow seen notifications to appear in the locked shade.
              */
             private fun isOnKeyguard(): Boolean =
-                if (notificationMinimalismPrototype() && minimalismShowOnLockedShade) {
+                if (NotificationMinimalismPrototype.V2.isEnabled) {
+                    false // disable this feature under this prototype
+                } else if (
+                    NotificationMinimalismPrototype.V1.isEnabled &&
+                        NotificationMinimalismPrototype.V1.showOnLockedShade
+                ) {
                     statusBarStateController.state == StatusBarState.KEYGUARD
                 } else {
                     keyguardRepository.isKeyguardShowing()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 36c12a7..4506385 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -24,47 +24,51 @@
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
+import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
 import javax.inject.Inject
 
 /**
- * Handles the attachment of [Coordinator]s to the [NotifPipeline] so that the
- * Coordinators can register their respective callbacks.
+ * Handles the attachment of [Coordinator]s to the [NotifPipeline] so that the Coordinators can
+ * register their respective callbacks.
  */
 interface NotifCoordinators : Coordinator, PipelineDumpable
 
 @CoordinatorScope
-class NotifCoordinatorsImpl @Inject constructor(
-        sectionStyleProvider: SectionStyleProvider,
-        featureFlags: FeatureFlags,
-        dataStoreCoordinator: DataStoreCoordinator,
-        hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
-        hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
-        keyguardCoordinator: KeyguardCoordinator,
-        rankingCoordinator: RankingCoordinator,
-        colorizedFgsCoordinator: ColorizedFgsCoordinator,
-        deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
-        bubbleCoordinator: BubbleCoordinator,
-        headsUpCoordinator: HeadsUpCoordinator,
-        gutsCoordinator: GutsCoordinator,
-        conversationCoordinator: ConversationCoordinator,
-        debugModeCoordinator: DebugModeCoordinator,
-        groupCountCoordinator: GroupCountCoordinator,
-        groupWhenCoordinator: GroupWhenCoordinator,
-        mediaCoordinator: MediaCoordinator,
-        preparationCoordinator: PreparationCoordinator,
-        remoteInputCoordinator: RemoteInputCoordinator,
-        rowAlertTimeCoordinator: RowAlertTimeCoordinator,
-        rowAppearanceCoordinator: RowAppearanceCoordinator,
-        stackCoordinator: StackCoordinator,
-        shadeEventCoordinator: ShadeEventCoordinator,
-        smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
-        viewConfigCoordinator: ViewConfigCoordinator,
-        visualStabilityCoordinator: VisualStabilityCoordinator,
-        sensitiveContentCoordinator: SensitiveContentCoordinator,
-        dismissibilityCoordinator: DismissibilityCoordinator,
-        dreamCoordinator: DreamCoordinator,
-        statsLoggerCoordinator: NotificationStatsLoggerCoordinator,
+class NotifCoordinatorsImpl
+@Inject
+constructor(
+    sectionStyleProvider: SectionStyleProvider,
+    featureFlags: FeatureFlags,
+    dataStoreCoordinator: DataStoreCoordinator,
+    hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
+    hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
+    keyguardCoordinator: KeyguardCoordinator,
+    rankingCoordinator: RankingCoordinator,
+    colorizedFgsCoordinator: ColorizedFgsCoordinator,
+    deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
+    bubbleCoordinator: BubbleCoordinator,
+    headsUpCoordinator: HeadsUpCoordinator,
+    gutsCoordinator: GutsCoordinator,
+    conversationCoordinator: ConversationCoordinator,
+    debugModeCoordinator: DebugModeCoordinator,
+    groupCountCoordinator: GroupCountCoordinator,
+    groupWhenCoordinator: GroupWhenCoordinator,
+    mediaCoordinator: MediaCoordinator,
+    preparationCoordinator: PreparationCoordinator,
+    remoteInputCoordinator: RemoteInputCoordinator,
+    rowAlertTimeCoordinator: RowAlertTimeCoordinator,
+    rowAppearanceCoordinator: RowAppearanceCoordinator,
+    stackCoordinator: StackCoordinator,
+    shadeEventCoordinator: ShadeEventCoordinator,
+    smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
+    viewConfigCoordinator: ViewConfigCoordinator,
+    visualStabilityCoordinator: VisualStabilityCoordinator,
+    sensitiveContentCoordinator: SensitiveContentCoordinator,
+    dismissibilityCoordinator: DismissibilityCoordinator,
+    dreamCoordinator: DreamCoordinator,
+    statsLoggerCoordinator: NotificationStatsLoggerCoordinator,
 ) : NotifCoordinators {
 
     private val mCoreCoordinators: MutableList<CoreCoordinator> = ArrayList()
@@ -114,6 +118,12 @@
         // Manually add Ordered Sections
         mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
         mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService
+        if (NotificationMinimalismPrototype.V2.isEnabled) {
+            mOrderedSections.add(keyguardCoordinator.unseenNotifSectioner) // Unseen (FGS)
+        }
+        if (PriorityPeopleSection.isEnabled) {
+            mOrderedSections.add(conversationCoordinator.priorityPeopleSectioner) // Priority People
+        }
         mOrderedSections.add(conversationCoordinator.peopleAlertingSectioner) // People Alerting
         if (!SortBySectionTimeFlag.isEnabled) {
             mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent
@@ -124,22 +134,26 @@
 
         sectionStyleProvider.setMinimizedSections(setOf(rankingCoordinator.minimizedSectioner))
         if (SortBySectionTimeFlag.isEnabled) {
-            sectionStyleProvider.setSilentSections(listOf(
+            sectionStyleProvider.setSilentSections(
+                listOf(
                     rankingCoordinator.silentSectioner,
                     rankingCoordinator.minimizedSectioner,
-            ))
+                )
+            )
         } else {
-            sectionStyleProvider.setSilentSections(listOf(
+            sectionStyleProvider.setSilentSections(
+                listOf(
                     conversationCoordinator.peopleSilentSectioner,
                     rankingCoordinator.silentSectioner,
                     rankingCoordinator.minimizedSectioner,
-            ))
+                )
+            )
         }
     }
 
     /**
-     * Sends the pipeline to each coordinator when the pipeline is ready to accept
-     * [Pluggable]s, [NotifCollectionListener]s and [NotifLifetimeExtender]s.
+     * Sends the pipeline to each coordinator when the pipeline is ready to accept [Pluggable]s,
+     * [NotifCollectionListener]s and [NotifLifetimeExtender]s.
      */
     override fun attach(pipeline: NotifPipeline) {
         for (c in mCoreCoordinators) {
@@ -155,10 +169,11 @@
      * As part of the NotifPipeline dumpable, dumps the list of coordinators; sections are omitted
      * as they are dumped in the RenderStageManager instead.
      */
-    override fun dumpPipeline(d: PipelineDumper) = with(d) {
-        dump("core coordinators", mCoreCoordinators)
-        dump("normal coordinators", mCoordinators)
-    }
+    override fun dumpPipeline(d: PipelineDumper) =
+        with(d) {
+            dump("core coordinators", mCoreCoordinators)
+            dump("normal coordinators", mCoordinators)
+        }
 
     companion object {
         private const val TAG = "NotifCoordinators"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 350e88e..caa6c17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -38,6 +38,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.Compile;
 import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -63,6 +65,7 @@
     public static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
     private final DelayableExecutor mDelayableExecutor;
     private final HeadsUpManager mHeadsUpManager;
+    private final SeenNotificationsInteractor mSeenNotificationsInteractor;
     private final ShadeAnimationInteractor mShadeAnimationInteractor;
     private final StatusBarStateController mStatusBarStateController;
     private final JavaAdapter mJavaAdapter;
@@ -101,6 +104,7 @@
             HeadsUpManager headsUpManager,
             ShadeAnimationInteractor shadeAnimationInteractor,
             JavaAdapter javaAdapter,
+            SeenNotificationsInteractor seenNotificationsInteractor,
             StatusBarStateController statusBarStateController,
             VisibilityLocationProvider visibilityLocationProvider,
             VisualStabilityProvider visualStabilityProvider,
@@ -109,6 +113,7 @@
         mHeadsUpManager = headsUpManager;
         mShadeAnimationInteractor = shadeAnimationInteractor;
         mJavaAdapter = javaAdapter;
+        mSeenNotificationsInteractor = seenNotificationsInteractor;
         mVisibilityLocationProvider = visibilityLocationProvider;
         mVisualStabilityProvider = visualStabilityProvider;
         mWakefulnessLifecycle = wakefulnessLifecycle;
@@ -142,8 +147,15 @@
     private final NotifStabilityManager mNotifStabilityManager =
             new NotifStabilityManager("VisualStabilityCoordinator") {
                 private boolean canMoveForHeadsUp(NotificationEntry entry) {
-                    return entry != null && mHeadsUpManager.isHeadsUpEntry(entry.getKey())
-                            && !mVisibilityLocationProvider.isInVisibleLocation(entry);
+                    if (entry == null) {
+                        return false;
+                    }
+                    boolean isTopUnseen = NotificationMinimalismPrototype.V2.isEnabled()
+                            && mSeenNotificationsInteractor.isTopUnseenNotification(entry);
+                    if (isTopUnseen || mHeadsUpManager.isHeadsUpEntry(entry.getKey())) {
+                        return !mVisibilityLocationProvider.isInVisibleLocation(entry);
+                    }
+                    return false;
                 }
 
                 @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
index 5c844bc..e2c9e02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
@@ -41,6 +41,9 @@
 
     /** Stats about the list of notifications attached to the shade */
     val notifStats = MutableStateFlow(NotifStats.empty)
+
+    /** The key of the top unseen notification */
+    val topUnseenNotificationKey = MutableStateFlow<String?>(null)
 }
 
 /** Represents the notification list, comprised of groups and individual notifications. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
index 1f7ab96..42828d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
@@ -17,7 +17,9 @@
 package com.android.systemui.statusbar.notification.domain.interactor
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
 import javax.inject.Inject
 import kotlinx.coroutines.flow.StateFlow
 
@@ -36,4 +38,15 @@
     fun setHasFilteredOutSeenNotifications(value: Boolean) {
         notificationListRepository.hasFilteredOutSeenNotifications.value = value
     }
+
+    /** Set the entry that is identified as the top unseen notification. */
+    fun setTopUnseenNotification(entry: NotificationEntry?) {
+        if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return
+        notificationListRepository.topUnseenNotificationKey.value = entry?.key
+    }
+
+    /** Determine if the given notification is the top unseen notification. */
+    fun isTopUnseenNotification(entry: NotificationEntry?): Boolean =
+        if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) false
+        else entry != null && notificationListRepository.topUnseenNotificationKey.value == entry.key
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 968b591..5a616df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -18,7 +18,7 @@
 
 import static android.graphics.PorterDuff.Mode.SRC_ATOP;
 
-import static com.android.systemui.Flags.notificationBackgroundTintOptimization;
+import static com.android.systemui.Flags.notificationFooterBackgroundTintOptimization;
 import static com.android.systemui.util.ColorUtilKt.hexColorString;
 
 import android.annotation.ColorInt;
@@ -407,7 +407,7 @@
         final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
         final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
         final @ColorInt int scHigh;
-        if (!notificationBackgroundTintOptimization()) {
+        if (!notificationFooterBackgroundTintOptimization()) {
             scHigh = Utils.getColorAttrDefaultColor(mContext,
                     com.android.internal.R.attr.materialColorSurfaceContainerHigh);
             if (scHigh != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index bfc5932..87f11f13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.interruption
 
+import android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST
 import android.app.Notification
 import android.app.Notification.BubbleMetadata
 import android.app.Notification.CATEGORY_EVENT
@@ -23,6 +24,8 @@
 import android.app.Notification.VISIBILITY_PRIVATE
 import android.app.NotificationManager.IMPORTANCE_DEFAULT
 import android.app.NotificationManager.IMPORTANCE_HIGH
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.PERMISSION_GRANTED
 import android.database.ContentObserver
 import android.hardware.display.AmbientDisplayConfiguration
 import android.os.Handler
@@ -234,6 +237,7 @@
     private val avalancheProvider: AvalancheProvider,
     private val systemClock: SystemClock,
     private val systemSettings: SystemSettings,
+    private val packageManager: PackageManager,
 ) :
     VisualInterruptionFilter(
         types = setOf(PEEK, PULSE),
@@ -241,9 +245,6 @@
     ) {
     val TAG = "AvalancheSuppressor"
 
-    override var reason: String = "avalanche"
-        protected set
-
     enum class State {
         ALLOW_CONVERSATION_AFTER_AVALANCHE,
         ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME,
@@ -252,24 +253,21 @@
         ALLOW_CATEGORY_EVENT,
         ALLOW_FSI_WITH_PERMISSION_ON,
         ALLOW_COLORIZED,
+        ALLOW_EMERGENCY,
         SUPPRESS
     }
 
     override fun shouldSuppress(entry: NotificationEntry): Boolean {
         if (!isCooldownEnabled()) {
-            reason = "FALSE avalanche cooldown setting DISABLED"
             return false
         }
         val timeSinceAvalancheMs = systemClock.currentTimeMillis() - avalancheProvider.startTime
         val timedOut = timeSinceAvalancheMs >= avalancheProvider.timeoutMs
         if (timedOut) {
-            reason = "FALSE avalanche event TIMED OUT. " +
-                    "${timeSinceAvalancheMs/1000} seconds since last avalanche"
             return false
         }
         val state = calculateState(entry)
         if (state != State.SUPPRESS) {
-            reason = "FALSE avalanche IN ALLOWLIST: $state"
             return false
         }
         return true
@@ -306,13 +304,20 @@
         if (entry.sbn.notification.isColorized) {
             return State.ALLOW_COLORIZED
         }
+        if (entry.sbn.notification.isColorized) {
+            return State.ALLOW_COLORIZED
+        }
+        if (
+            packageManager.checkPermission(RECEIVE_EMERGENCY_BROADCAST, entry.sbn.packageName) ==
+                PERMISSION_GRANTED
+        ) {
+            return State.ALLOW_EMERGENCY
+        }
         return State.SUPPRESS
     }
 
     private fun isCooldownEnabled(): Boolean {
-        return systemSettings.getInt(
-            Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
-            /* def */ 1
-        ) == 1
+        return systemSettings.getInt(Settings.System.NOTIFICATION_COOLDOWN_ENABLED, /* def */ 1) ==
+            1
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index e6d97c2..f68e194 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.statusbar.notification.interruption
 
+import android.content.pm.PackageManager
 import android.hardware.display.AmbientDisplayConfiguration
 import android.os.Handler
 import android.os.PowerManager
@@ -63,7 +64,8 @@
     private val uiEventLogger: UiEventLogger,
     private val userTracker: UserTracker,
     private val avalancheProvider: AvalancheProvider,
-    private val systemSettings: SystemSettings
+    private val systemSettings: SystemSettings,
+    private val packageManager: PackageManager
 ) : VisualInterruptionDecisionProvider {
 
     init {
@@ -172,7 +174,9 @@
         addFilter(AlertKeyguardVisibilitySuppressor(keyguardNotificationVisibilityProvider))
 
         if (NotificationAvalancheSuppression.isEnabled) {
-            addFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings))
+            addFilter(
+                AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+            )
             avalancheProvider.register()
         }
         started = true
@@ -232,14 +236,17 @@
 
     private fun makeLoggablePeekDecision(entry: NotificationEntry): LoggableDecision =
         checkConditions(PEEK)
-            ?: checkFilters(PEEK, entry) ?: checkSuppressInterruptions(entry)
-                ?: checkSuppressAwakeInterruptions(entry) ?: checkSuppressAwakeHeadsUp(entry)
-                ?: LoggableDecision.unsuppressed
+            ?: checkFilters(PEEK, entry)
+            ?: checkSuppressInterruptions(entry)
+            ?: checkSuppressAwakeInterruptions(entry)
+            ?: checkSuppressAwakeHeadsUp(entry)
+            ?: LoggableDecision.unsuppressed
 
     private fun makeLoggablePulseDecision(entry: NotificationEntry): LoggableDecision =
         checkConditions(PULSE)
-            ?: checkFilters(PULSE, entry) ?: checkSuppressInterruptions(entry)
-                ?: LoggableDecision.unsuppressed
+            ?: checkFilters(PULSE, entry)
+            ?: checkSuppressInterruptions(entry)
+            ?: LoggableDecision.unsuppressed
 
     override fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision =
         traceSection("VisualInterruptionDecisionProviderImpl#makeAndLogBubbleDecision") {
@@ -252,8 +259,10 @@
 
     private fun makeLoggableBubbleDecision(entry: NotificationEntry): LoggableDecision =
         checkConditions(BUBBLE)
-            ?: checkFilters(BUBBLE, entry) ?: checkSuppressInterruptions(entry)
-                ?: checkSuppressAwakeInterruptions(entry) ?: LoggableDecision.unsuppressed
+            ?: checkFilters(BUBBLE, entry)
+            ?: checkSuppressInterruptions(entry)
+            ?: checkSuppressAwakeInterruptions(entry)
+            ?: LoggableDecision.unsuppressed
 
     private fun logDecision(
         type: VisualInterruptionType,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
index 89aa3ab..9e0dd8fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
@@ -21,6 +21,7 @@
 import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_HEADS_UP;
 import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS;
 import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PRIORITY_PEOPLE;
 import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
 
 import android.annotation.Nullable;
@@ -130,7 +131,8 @@
             case BUCKET_HEADS_UP: return Notifications.Notification.SECTION_HEADS_UP;
             case BUCKET_FOREGROUND_SERVICE:
                 return Notifications.Notification.SECTION_FOREGROUND_SERVICE;
-            case BUCKET_PEOPLE: return Notifications.Notification.SECTION_PEOPLE;
+            case BUCKET_PEOPLE, BUCKET_PRIORITY_PEOPLE:
+                return Notifications.Notification.SECTION_PEOPLE;
             case BUCKET_ALERTING: return Notifications.Notification.SECTION_ALERTING;
             case BUCKET_SILENT: return Notifications.Notification.SECTION_SILENT;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 61cdea1..d2d0aaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -17,6 +17,8 @@
 package com.android.systemui.statusbar.notification.row;
 
 import static com.android.systemui.Flags.notificationBackgroundTintOptimization;
+import static com.android.systemui.statusbar.notification.row.ExpandableView.ClipSide.BOTTOM;
+import static com.android.systemui.statusbar.notification.row.ExpandableView.ClipSide.TOP;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -38,10 +40,12 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.res.R;
+import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.FakeShadowView;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.SourceType;
+import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -353,12 +357,13 @@
     @Override
     public long performRemoveAnimation(long duration, long delay, float translationDirection,
             boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable,
-            AnimatorListenerAdapter animationListener) {
+            AnimatorListenerAdapter animationListener, ClipSide clipSide) {
         enableAppearDrawing(true);
         mIsHeadsUpAnimation = isHeadsUpAnimation;
         if (mDrawingAppearAnimation) {
             startAppearAnimation(false /* isAppearing */, translationDirection,
-                    delay, duration, onStartedRunnable, onFinishedRunnable, animationListener);
+                    delay, duration, onStartedRunnable, onFinishedRunnable, animationListener,
+                    clipSide);
         } else {
             if (onStartedRunnable != null) {
                 onStartedRunnable.run();
@@ -377,13 +382,13 @@
         mIsHeadsUpAnimation = isHeadsUpAppear;
         if (mDrawingAppearAnimation) {
             startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay,
-                    duration, null, null, null);
+                    duration, null, null, null, ClipSide.BOTTOM);
         }
     }
 
     private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay,
             long duration, final Runnable onStartedRunnable, final Runnable onFinishedRunnable,
-            AnimatorListenerAdapter animationListener) {
+            AnimatorListenerAdapter animationListener, ClipSide clipSide) {
         mAnimationTranslationY = translationDirection * getActualHeight();
         cancelAppearAnimation();
         if (mAppearAnimationFraction == -1.0f) {
@@ -405,9 +410,16 @@
             mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE;
             targetValue = 0.0f;
         }
+
+        if (NotificationHeadsUpCycling.isEnabled()) {
+            // TODO(b/316404716): add avalanche filtering
+            mCurrentAppearInterpolator = Interpolators.LINEAR;
+        }
+
         mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction,
                 targetValue);
-        if (NotificationsImprovedHunAnimation.isEnabled()) {
+        if (NotificationsImprovedHunAnimation.isEnabled()
+                || NotificationHeadsUpCycling.isEnabled()) {
             mAppearAnimator.setInterpolator(mCurrentAppearInterpolator);
         } else {
             mAppearAnimator.setInterpolator(Interpolators.LINEAR);
@@ -417,7 +429,12 @@
         mAppearAnimator.addUpdateListener(animation -> {
             mAppearAnimationFraction = (float) animation.getAnimatedValue();
             updateAppearAnimationAlpha();
-            updateAppearRect();
+            if (NotificationHeadsUpCycling.isEnabled()) {
+                // For cycling out, we want the HUN to be clipped from the top.
+                updateAppearRect(clipSide);
+            } else {
+                updateAppearRect();
+            }
             invalidate();
         });
         if (animationListener != null) {
@@ -425,7 +442,11 @@
         }
         // we need to apply the initial state already to avoid drawn frames in the wrong state
         updateAppearAnimationAlpha();
-        updateAppearRect();
+        if (NotificationHeadsUpCycling.isEnabled()) {
+            updateAppearRect(clipSide);
+        } else {
+            updateAppearRect();
+        }
         mAppearAnimator.addListener(new AnimatorListenerAdapter() {
             private boolean mRunWithoutInterruptions;
 
@@ -507,14 +528,18 @@
         enableAppearDrawing(false);
     }
 
-    private void updateAppearRect() {
+    /**
+     * Update the View's Rect clipping to fit the appear animation
+     * @param clipSide Which side if view we want to clip from
+     */
+    private void updateAppearRect(ClipSide clipSide) {
         float interpolatedFraction =
-                NotificationsImprovedHunAnimation.isEnabled() ? mAppearAnimationFraction
+                NotificationsImprovedHunAnimation.isEnabled()
+                        || NotificationHeadsUpCycling.isEnabled() ? mAppearAnimationFraction
                         : mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction);
         mAppearAnimationTranslation = (1.0f - interpolatedFraction) * mAnimationTranslationY;
-        final int actualHeight = getActualHeight();
-        float bottom = actualHeight * interpolatedFraction;
-
+        final int fullHeight = getActualHeight();
+        float height = fullHeight * interpolatedFraction;
         if (mTargetPoint != null) {
             int width = getWidth();
             float fraction = 1 - mAppearAnimationFraction;
@@ -523,13 +548,26 @@
                     mAnimationTranslationY
                             + (mAnimationTranslationY - mTargetPoint.y) * fraction,
                     width - (width - mTargetPoint.x) * fraction,
-                    actualHeight - (actualHeight - mTargetPoint.y) * fraction);
+                    fullHeight - (fullHeight - mTargetPoint.y) * fraction);
         } else {
-            setOutlineRect(0, mAppearAnimationTranslation, getWidth(),
-                    bottom + mAppearAnimationTranslation);
+            if (clipSide == TOP) {
+                setOutlineRect(
+                        0,
+                        /* top= */ fullHeight - height,
+                        getWidth(),
+                        /* bottom= */ fullHeight
+                );
+            } else if (clipSide == BOTTOM) {
+                setOutlineRect(0, mAppearAnimationTranslation, getWidth(),
+                        height + mAppearAnimationTranslation);
+            }
         }
     }
 
+    private void updateAppearRect() {
+        updateAppearRect(ClipSide.BOTTOM);
+    }
+
     private float getInterpolatedAppearAnimationFraction() {
 
         if (mAppearAnimationFraction >= 0) {
@@ -539,11 +577,36 @@
     }
 
     private void updateAppearAnimationAlpha() {
-        float contentAlphaProgress = MathUtils.constrain(mAppearAnimationFraction,
-                ALPHA_APPEAR_START_FRACTION, ALPHA_APPEAR_END_FRACTION);
-        float range = ALPHA_APPEAR_END_FRACTION - ALPHA_APPEAR_START_FRACTION;
-        float alpha = (contentAlphaProgress - ALPHA_APPEAR_START_FRACTION) / range;
-        setContentAlpha(Interpolators.ALPHA_IN.getInterpolation(alpha));
+        updateAppearAnimationContentAlpha(
+                mAppearAnimationFraction,
+                ALPHA_APPEAR_START_FRACTION,
+                ALPHA_APPEAR_END_FRACTION,
+                Interpolators.ALPHA_IN
+        );
+    }
+
+    /**
+     * Update the alpha value of the content view during the appear animation. We suppose that the
+     * content alpha changes from 0 to 1 during some part of the appear animation.
+     * @param appearFraction the current appearFraction, should be in the range of [0, 1], where
+     *                       1 represents fully appeared
+     * @param startFraction the appear fraction when the content view should be
+     *      *                    fully transparent
+     * @param endFraction the appear fraction when the content view should be
+     *                    fully in-transparent, should be greater or equals to startFraction
+     * @param interpolator the interpolator to update the alpha
+     */
+    private void updateAppearAnimationContentAlpha(
+            float appearFraction,
+            float startFraction,
+            float endFraction,
+            Interpolator interpolator
+    ) {
+        float contentAlphaProgress = MathUtils.constrain(appearFraction, startFraction,
+                endFraction);
+        float range = endFraction - startFraction;
+        float alpha = (contentAlphaProgress - startFraction) / range;
+        setContentAlpha(interpolator.getInterpolation(alpha));
     }
 
     private void setContentAlpha(float contentAlpha) {
@@ -745,6 +808,12 @@
         }
     }
 
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch(
+                getClass().getSimpleName(), ev, super.dispatchTouchEvent(ev));
+    }
+
     /**
      * SourceType which should be reset when this View is detached
      * @param sourceType will be reset on View detached
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 5e3df7b..edd2961 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -101,6 +101,7 @@
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
+import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactMessagingTemplateViewWrapper;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
 import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -375,6 +376,8 @@
             };
 
     private OnClickListener mOnClickListener;
+    @Nullable
+    private OnClickListener mBubbleClickListener;
     private OnDragSuccessListener mOnDragSuccessListener;
     private boolean mHeadsupDisappearRunning;
     private View mChildAfterViewWhenDismissed;
@@ -588,7 +591,9 @@
             mMenuRow.setAppName(mAppName);
         }
         if (mIsSummaryWithChildren) {
-            if (!AsyncGroupHeaderViewInflation.isEnabled()) {
+            if (AsyncGroupHeaderViewInflation.isEnabled()) {
+                mChildrenContainer.updateGroupHeaderExpandState();
+            } else {
                 // We create the header from the background thread instead
                 mChildrenContainer.recreateNotificationHeader(mExpandClickListener,
                         isConversation());
@@ -841,6 +846,16 @@
     }
 
     /**
+     *
+     * @return true when compact version of Heads Up is on the screen.
+     */
+    public boolean isCompactConversationHeadsUpOnScreen() {
+        final NotificationViewWrapper viewWrapper =
+                getVisibleNotificationViewWrapper();
+
+        return viewWrapper instanceof NotificationCompactMessagingTemplateViewWrapper;
+    }
+    /**
      * @see NotificationChildrenContainer#setUntruncatedChildCount(int)
      */
     public void setUntruncatedChildCount(int childCount) {
@@ -1234,14 +1249,19 @@
     /**
      * The click listener for the bubble button.
      */
+    @Nullable
     public View.OnClickListener getBubbleClickListener() {
-        return v -> {
-            if (mBubblesManagerOptional.isPresent()) {
-                mBubblesManagerOptional.get()
-                        .onUserChangedBubble(mEntry, !mEntry.isBubble() /* createBubble */);
-            }
-            mHeadsUpManager.removeNotification(mEntry.getKey(), true /* releaseImmediately */);
-        };
+        return mBubbleClickListener;
+    }
+
+    /**
+     * Sets the click listener for the bubble button.
+     */
+    public void setBubbleClickListener(@Nullable OnClickListener l) {
+        mBubbleClickListener = l;
+        // ensure listener is passed to the content views
+        mPrivateLayout.updateBubbleButton(mEntry);
+        mPublicLayout.updateBubbleButton(mEntry);
     }
 
     /**
@@ -2781,7 +2801,10 @@
         }
     }
 
-    protected void expandNotification() {
+    /**
+     * Triggers expand click listener to expand the notification.
+     */
+    public void expandNotification() {
         mExpandClickListener.onClick(this);
     }
 
@@ -3069,7 +3092,7 @@
             boolean isHeadsUpAnimation,
             Runnable onStartedRunnable,
             Runnable onFinishedRunnable,
-            AnimatorListenerAdapter animationListener) {
+            AnimatorListenerAdapter animationListener, ClipSide clipSide) {
         if (mMenuRow != null && mMenuRow.isMenuVisible()) {
             Animator anim = getTranslateViewAnimator(0f, null /* listener */);
             if (anim != null) {
@@ -3085,7 +3108,7 @@
                     public void onAnimationEnd(Animator animation) {
                         ExpandableNotificationRow.super.performRemoveAnimation(
                                 duration, delay, translationDirection, isHeadsUpAnimation,
-                                null, onFinishedRunnable, animationListener);
+                                null, onFinishedRunnable, animationListener, ClipSide.BOTTOM);
                     }
                 });
                 anim.start();
@@ -3093,7 +3116,8 @@
             }
         }
         return super.performRemoveAnimation(duration, delay, translationDirection,
-                isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener);
+                isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener,
+                clipSide);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 05e8717..2af119f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -362,17 +362,17 @@
 
     /**
      * Perform a remove animation on this view.
-     * @param duration The duration of the remove animation.
-     * @param delay The delay of the animation
+     *
+     * @param duration             The duration of the remove animation.
+     * @param delay                The delay of the animation
      * @param translationDirection The direction value from [-1 ... 1] indicating in which the
      *                             animation should be performed. A value of -1 means that The
      *                             remove animation should be performed upwards,
      *                             such that the  child appears to be going away to the top. 1
      *                             Should mean the opposite.
-     * @param isHeadsUpAnimation Is this a headsUp animation.
-     * @param onFinishedRunnable A runnable which should be run when the animation is finished.
-     * @param animationListener An animation listener to add to the animation.
-     *
+     * @param isHeadsUpAnimation   Is this a headsUp animation.
+     * @param onFinishedRunnable   A runnable which should be run when the animation is finished.
+     * @param animationListener    An animation listener to add to the animation.
      * @return The additional delay, in milliseconds, that this view needs to add before the
      * animation starts.
      */
@@ -380,7 +380,12 @@
             long delay, float translationDirection, boolean isHeadsUpAnimation,
             Runnable onStartedRunnable,
             Runnable onFinishedRunnable,
-            AnimatorListenerAdapter animationListener);
+            AnimatorListenerAdapter animationListener, ClipSide clipSide);
+
+    public enum ClipSide {
+        TOP,
+        BOTTOM
+    }
 
     public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
         performAddAnimation(delay, duration, isHeadsUpAppear, null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
index 816e5c1..9d0fcd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.statusbar.notification.row
 
 import android.app.Flags
+import android.os.SystemProperties
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
 import javax.inject.Inject
 
 /**
@@ -27,11 +29,19 @@
     fun shouldApplyCompactStyle(): Boolean
 }
 
-class HeadsUpStyleProviderImpl @Inject constructor() : HeadsUpStyleProvider {
+class HeadsUpStyleProviderImpl
+@Inject
+constructor(private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore) :
+    HeadsUpStyleProvider {
 
-    /**
-     * TODO(b/270709257) This feature is under development. This method returns Compact when the
-     *   flag is enabled for fish fooding purpose.
-     */
-    override fun shouldApplyCompactStyle(): Boolean = Flags.compactHeadsUpNotification()
+    override fun shouldApplyCompactStyle(): Boolean {
+        return Flags.compactHeadsUpNotification() && (isInImmersiveMode() || alwaysShow())
+    }
+
+    private fun isInImmersiveMode() =
+        statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value
+
+    /** developer setting to always show Minimal HUN, even if the device is not in full screen */
+    private fun alwaysShow() =
+        SystemProperties.getBoolean("persist.compact_heads_up_notification.always_show", false)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index 162e8af..291dc13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -252,7 +252,7 @@
             float translationDirection, boolean isHeadsUpAnimation,
             Runnable onStartedRunnable,
             Runnable onFinishedRunnable,
-            AnimatorListenerAdapter animationListener) {
+            AnimatorListenerAdapter animationListener, ClipSide clipSide) {
         // TODO: Use duration
         if (onStartedRunnable != null) {
             onStartedRunnable.run();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/ActivatableNotificationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/ActivatableNotificationViewBinder.kt
index 9a54de1..2527af8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/ActivatableNotificationViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/ActivatableNotificationViewBinder.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.row.ui.viewbinder
 
+import android.util.Log
 import android.view.MotionEvent
 import android.view.View
 import android.view.View.OnTouchListener
@@ -72,7 +73,6 @@
     var isTouchEnabled = false
 
     override fun onTouch(v: View, ev: MotionEvent): Boolean {
-        val result = false
         if (ev.action == MotionEvent.ACTION_UP) {
             view.setLastActionUpTime(ev.eventTime)
         }
@@ -82,13 +82,22 @@
         }
         if (ev.action == MotionEvent.ACTION_UP) {
             // If this is a false tap, capture the even so it doesn't result in a click.
-            return falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)
+            return falsingManager.isFalseTap(FalsingManager.LOW_PENALTY).also {
+                if (it) {
+                    Log.d(v::class.simpleName ?: TAG, "capturing false tap")
+                }
+            }
         }
-        return result
+        return false
     }
 
     override fun onInterceptTouchEvent(ev: MotionEvent): Boolean = false
 
     /** Use [onTouch] instead. */
     override fun onTouchEvent(ev: MotionEvent): Boolean = false
+
+    companion object {
+        private const val TAG = "ActivatableNotificationViewBinder"
+    }
 }
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactHeadsUpTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactHeadsUpTemplateViewWrapper.kt
index ce87d2f..3a5f3b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactHeadsUpTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactHeadsUpTemplateViewWrapper.kt
@@ -24,7 +24,7 @@
 /**
  * Compact Heads up Notifications template that doesn't set feedback icon and audibly alert icons
  */
-class NotificationCompactHeadsUpTemplateViewWrapper(
+open class NotificationCompactHeadsUpTemplateViewWrapper(
     ctx: Context,
     view: View,
     row: ExpandableNotificationRow
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactMessagingTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactMessagingTemplateViewWrapper.kt
new file mode 100644
index 0000000..bb40b56
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactMessagingTemplateViewWrapper.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.row.wrapper
+
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import com.android.internal.R
+import com.android.internal.widget.CachingIconView
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+
+/** Wraps a notification containing a messaging or conversation template */
+class NotificationCompactMessagingTemplateViewWrapper
+constructor(ctx: Context, view: View, row: ExpandableNotificationRow) :
+    NotificationCompactHeadsUpTemplateViewWrapper(ctx, view, row) {
+
+    private val compactMessagingView: ViewGroup = requireNotNull(view as? ViewGroup)
+
+    private var conversationIconView: CachingIconView? = null
+    private var expandBtn: View? = null
+    override fun onContentUpdated(row: ExpandableNotificationRow?) {
+        resolveViews()
+        super.onContentUpdated(row)
+    }
+
+    private fun resolveViews() {
+        conversationIconView = compactMessagingView.requireViewById(R.id.conversation_icon)
+        expandBtn = compactMessagingView.requireViewById(R.id.expand_button)
+    }
+
+    override fun updateTransformedTypes() {
+        super.updateTransformedTypes()
+
+        addViewsTransformingToSimilar(
+            conversationIconView,
+            expandBtn,
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index 4244542..22b95ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -74,6 +74,8 @@
                 return new NotificationCallTemplateViewWrapper(ctx, v, row);
             } else if ("compactHUN".equals((v.getTag()))) {
                 return new NotificationCompactHeadsUpTemplateViewWrapper(ctx, v, row);
+            } else if ("compactMessagingHUN".equals((v.getTag()))) {
+                return new NotificationCompactMessagingTemplateViewWrapper(ctx, v, row);
             }
 
             if (row.getEntry().getSbn().getNotification().isStyle(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
index 0344b32..d6c73a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
@@ -23,8 +23,8 @@
 /** Helper for reading or using the heads-up cycling flag state. */
 @Suppress("NOTHING_TO_INLINE")
 object NotificationHeadsUpCycling {
-    /** The aconfig flag name */
-    const val FLAG_NAME = Flags.FLAG_NOTIFICATION_HEADS_UP_CYCLING
+    /** The aconfig flag name - enable this feature when FLAG_NOTIFICATION_THROTTLE_HUN is on. */
+    const val FLAG_NAME = Flags.FLAG_NOTIFICATION_THROTTLE_HUN
 
     /** A token used for dependency declaration */
     val token: FlagToken
@@ -33,7 +33,12 @@
     /** Is the heads-up cycling animation enabled */
     @JvmStatic
     inline val isEnabled
-        get() = Flags.notificationContentAlphaOptimization()
+        get() = Flags.notificationThrottleHun()
+
+    /** Whether to animate the bottom line when transiting from a tall HUN to a short HUN */
+    @JvmStatic
+    inline val animateTallToShort
+        get() = false
 
     /**
      * Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt
new file mode 100644
index 0000000..bf37036
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.shared
+
+import android.os.SystemProperties
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the minimalism prototype flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationMinimalismPrototype {
+
+    val version: Int by lazy {
+        SystemProperties.getInt("persist.notification_minimalism_prototype.version", 2)
+    }
+
+    object V1 {
+        /** The aconfig flag name */
+        const val FLAG_NAME = Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE
+
+        /** A token used for dependency declaration */
+        val token: FlagToken
+            get() = FlagToken(FLAG_NAME, isEnabled)
+
+        /** Is the heads-up cycling animation enabled */
+        @JvmStatic
+        inline val isEnabled
+            get() = Flags.notificationMinimalismPrototype() && version == 1
+
+        /**
+         * the prototype will now show seen notifications on the locked shade by default, but this
+         * property read allows that to be quickly disabled for testing
+         */
+        val showOnLockedShade: Boolean
+            get() =
+                if (isUnexpectedlyInLegacyMode()) false
+                else
+                    SystemProperties.getBoolean(
+                        "persist.notification_minimalism_prototype.show_on_locked_shade",
+                        true
+                    )
+
+        /** gets the configurable max number of notifications */
+        val maxNotifs: Int
+            get() =
+                if (isUnexpectedlyInLegacyMode()) -1
+                else
+                    SystemProperties.getInt(
+                        "persist.notification_minimalism_prototype.lock_screen_max_notifs",
+                        1
+                    )
+
+        /**
+         * Called to ensure code is only run when the flag is enabled. This protects users from the
+         * unintended behaviors caused by accidentally running new logic, while also crashing on an
+         * eng build to ensure that the refactor author catches issues in testing.
+         */
+        @JvmStatic
+        inline fun isUnexpectedlyInLegacyMode() =
+            RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+        /**
+         * Called to ensure code is only run when the flag is disabled. This will throw an exception
+         * if the flag is enabled to ensure that the refactor author catches issues in testing.
+         */
+        @JvmStatic
+        inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+    }
+    object V2 {
+        const val FLAG_NAME = Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE
+
+        /** A token used for dependency declaration */
+        val token: FlagToken
+            get() = FlagToken(FLAG_NAME, isEnabled)
+
+        /** Is the heads-up cycling animation enabled */
+        @JvmStatic
+        inline val isEnabled
+            get() = Flags.notificationMinimalismPrototype() && version == 2
+
+        /**
+         * The prototype will (by default) use a promoter to ensure that the top unseen notification
+         * is not grouped, but this property read allows that behavior to be disabled.
+         */
+        val ungroupTopUnseen: Boolean
+            get() =
+                if (isUnexpectedlyInLegacyMode()) false
+                else
+                    SystemProperties.getBoolean(
+                        "persist.notification_minimalism_prototype.ungroup_top_unseen",
+                        true
+                    )
+
+        /**
+         * Called to ensure code is only run when the flag is enabled. This protects users from the
+         * unintended behaviors caused by accidentally running new logic, while also crashing on an
+         * eng build to ensure that the refactor author catches issues in testing.
+         */
+        @JvmStatic
+        inline fun isUnexpectedlyInLegacyMode() =
+            RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+        /**
+         * Called to ensure code is only run when the flag is disabled. This will throw an exception
+         * if the flag is enabled to ensure that the refactor author catches issues in testing.
+         */
+        @JvmStatic
+        inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/PriorityPeopleSection.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/PriorityPeopleSection.kt
new file mode 100644
index 0000000..472fd95
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/PriorityPeopleSection.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for com.android.systemui.Flags.FLAG_PRIORITY_PEOPLE_SECTION */
+@Suppress("NOTHING_TO_INLINE")
+object PriorityPeopleSection {
+    const val FLAG_NAME = Flags.FLAG_PRIORITY_PEOPLE_SECTION
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Are sections sorted by time? */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.priorityPeopleSection()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index e520957..5f4e832 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -293,6 +293,8 @@
     }
 
     String getAvalancheShowingHunKey() {
+        // If we don't have a previous showing hun, we don't consider the showing hun as avalanche
+        if (isNullAvalancheKey(getAvalanchePreviousHunKey())) return "";
         return mAvalancheController.getShowingHunKey();
     }
 
@@ -300,6 +302,11 @@
         return mAvalancheController.getPreviousHunKey();
     }
 
+    boolean isNullAvalancheKey(String key) {
+        if (key == null || key.isEmpty()) return true;
+        return key.equals("HeadsUpEntry null") || key.equals("HeadsUpEntry.mEntry null");
+    }
+
     void setOverExpansion(float overExpansion) {
         mOverExpansion = overExpansion;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
index 5551ab4..bd7bd59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
@@ -70,13 +70,14 @@
     }
 
     override fun performRemoveAnimation(
-        duration: Long,
-        delay: Long,
-        translationDirection: Float,
-        isHeadsUpAnimation: Boolean,
-        onStartedRunnable: Runnable?,
-        onFinishedRunnable: Runnable?,
-        animationListener: AnimatorListenerAdapter?
+            duration: Long,
+            delay: Long,
+            translationDirection: Float,
+            isHeadsUpAnimation: Boolean,
+            onStartedRunnable: Runnable?,
+            onFinishedRunnable: Runnable?,
+            animationListener: AnimatorListenerAdapter?,
+            clipSide: ClipSide
     ): Long {
         return 0
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 92c597c..48796d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -439,6 +439,15 @@
         Trace.endSection();
     }
 
+    /**
+     * Update the expand state of the group header.
+     */
+    public void updateGroupHeaderExpandState() {
+        if (mGroupHeaderWrapper != null) {
+            mGroupHeaderWrapper.setExpanded(mChildrenExpanded);
+        }
+    }
+
     private void removeGroupHeader() {
         if (mGroupHeader == null) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt
index 31f4857..fc28a99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt
@@ -3,15 +3,22 @@
 import android.annotation.IntDef
 
 /**
- * For now, declare the available notification buckets (sections) here so that other
- * presentation code can decide what to do based on an entry's buckets
+ * For now, declare the available notification buckets (sections) here so that other presentation
+ * code can decide what to do based on an entry's buckets
  */
 @Retention(AnnotationRetention.SOURCE)
 @IntDef(
-        prefix = ["BUCKET_"],
-        value = [
-            BUCKET_UNKNOWN, BUCKET_MEDIA_CONTROLS, BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE,
-            BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT
+    prefix = ["BUCKET_"],
+    value =
+        [
+            BUCKET_UNKNOWN,
+            BUCKET_MEDIA_CONTROLS,
+            BUCKET_HEADS_UP,
+            BUCKET_FOREGROUND_SERVICE,
+            BUCKET_PRIORITY_PEOPLE,
+            BUCKET_PEOPLE,
+            BUCKET_ALERTING,
+            BUCKET_SILENT
         ]
 )
 annotation class PriorityBucket
@@ -20,6 +27,7 @@
 const val BUCKET_MEDIA_CONTROLS = 1
 const val BUCKET_HEADS_UP = 2
 const val BUCKET_FOREGROUND_SERVICE = 3
+const val BUCKET_PRIORITY_PEOPLE = 7
 const val BUCKET_PEOPLE = 4
 const val BUCKET_ALERTING = 5
 const val BUCKET_SILENT = 6
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 3bd8735..17b54c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -112,6 +112,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
+import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
 import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
 import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
@@ -126,8 +127,8 @@
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
 import com.android.systemui.util.Assert;
 import com.android.systemui.util.ColorUtilKt;
-import com.android.systemui.util.Compile;
 import com.android.systemui.util.DumpUtilsKt;
+import com.android.systemui.util.ListenerSet;
 
 import com.google.errorprone.annotations.CompileTimeConstant;
 
@@ -152,11 +153,9 @@
 public class NotificationStackScrollLayout
         extends ViewGroup
         implements Dumpable, NotificationScrollView {
-
     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
     private static final String TAG = "StackScroller";
     private static final boolean SPEW = Log.isLoggable(TAG, Log.VERBOSE);
-    private static final boolean DEBUG_UPDATE_SIDE_PADDING = Compile.IS_DEBUG;
 
     private boolean mShadeNeedsToClose = false;
 
@@ -256,6 +255,7 @@
      * The raw amount of the overScroll on the bottom, which is not rubber-banded.
      */
     private float mOverScrolledBottomPixels;
+    private ListenerSet<Runnable> mStackHeightChangedListeners = new ListenerSet<>();
     private NotificationLogger.OnChildLocationsChangedListener mListener;
     private OnNotificationLocationsChangedListener mLocationsChangedListener;
     private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
@@ -318,7 +318,7 @@
             = new ViewTreeObserver.OnPreDrawListener() {
         @Override
         public boolean onPreDraw() {
-            if (SceneContainerFlag.isEnabled() && !mChildrenUpdateRequested) {
+            if (SceneContainerFlag.isEnabled()) {
                 getViewTreeObserver().removeOnPreDrawListener(this);
                 return true;
             }
@@ -674,7 +674,7 @@
     void setOverExpansion(float margin) {
         mAmbientState.setOverExpansion(margin);
         if (notificationOverExpansionClippingFix() && !SceneContainerFlag.isEnabled()) {
-            setRoundingClippingYTranslation((int) margin);
+            setRoundingClippingYTranslation(mShouldUseSplitNotificationShade ? (int) margin : 0);
         }
         updateStackPosition();
         requestChildrenUpdate();
@@ -933,11 +933,6 @@
                         + " mSkinnyNotifsInLandscape=" + mSkinnyNotifsInLandscape;
         mLastInitViewElapsedRealtime = SystemClock.elapsedRealtime();
 
-        if (DEBUG_UPDATE_SIDE_PADDING) {
-            Log.v(TAG, "initView @ elapsedRealtime " + mLastInitViewElapsedRealtime + ": "
-                    + mLastInitViewDumpString);
-        }
-
         mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
         mStackScrollAlgorithm.initView(context);
         mStateAnimator.initView(context);
@@ -967,12 +962,6 @@
                 + " orientation=" + orientation;
         mLastUpdateSidePaddingElapsedRealtime = SystemClock.elapsedRealtime();
 
-        if (DEBUG_UPDATE_SIDE_PADDING) {
-            Log.v(TAG,
-                    "updateSidePadding @ elapsedRealtime " + mLastUpdateSidePaddingElapsedRealtime
-                            + ": " + mLastUpdateSidePaddingDumpString);
-        }
-
         if (viewWidth == 0) {
             Log.e(TAG, "updateSidePadding: viewWidth is zero");
             mSidePaddings = mMinimumPaddings;
@@ -1096,6 +1085,10 @@
         for (int i = 0; i < size; i++) {
             measureChild(getChildAt(i), childWidthSpec, childHeightSpec);
         }
+        if (SceneContainerFlag.isEnabled()) {
+            setMaxLayoutHeight(getMeasuredHeight());
+            updateContentHeight();
+        }
         Trace.endSection();
     }
 
@@ -1105,6 +1098,22 @@
         super.requestLayout();
     }
 
+    private void notifyStackHeightChangedListeners() {
+        for (Runnable listener : mStackHeightChangedListeners) {
+            listener.run();
+        }
+    }
+
+    @Override
+    public void addStackHeightChangedListener(@NonNull Runnable runnable) {
+        mStackHeightChangedListeners.addIfAbsent(runnable);
+    }
+
+    @Override
+    public void removeStackHeightChangedListener(@NonNull Runnable runnable) {
+        mStackHeightChangedListeners.remove(runnable);
+    }
+
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         if (!mSuppressChildrenMeasureAndLayout) {
@@ -1123,8 +1132,10 @@
                         (int) height);
             }
         }
-        setMaxLayoutHeight(getHeight());
-        updateContentHeight();
+        if (!SceneContainerFlag.isEnabled()) {
+            setMaxLayoutHeight(getHeight());
+            updateContentHeight();
+        }
         clampScrollPosition();
         requestChildrenUpdate();
         updateFirstAndLastBackgroundViews();
@@ -1185,8 +1196,8 @@
     }
 
     @Override
-    public void setStackHeightConsumer(@Nullable Consumer<Float> consumer) {
-        mScrollViewFields.setStackHeightConsumer(consumer);
+    public void setCurrentGestureOverscrollConsumer(@Nullable Consumer<Boolean> consumer) {
+        mScrollViewFields.setCurrentGestureOverscrollConsumer(consumer);
     }
 
     @Override
@@ -1477,9 +1488,10 @@
     public void setExpandedHeight(float height) {
         final boolean skipHeightUpdate = shouldSkipHeightUpdate();
 
-        // when scene framework is enabled, updateStackPosition is already called by
-        // updateTopPadding every time the stack moves, so skip it here to avoid flickering.
-        if (!SceneContainerFlag.isEnabled()) {
+        // when scene framework is enabled and in single shade, updateStackPosition is already
+        // called by updateTopPadding every time the stack moves, so skip it here to avoid
+        // flickering.
+        if (!SceneContainerFlag.isEnabled() || mShouldUseSplitNotificationShade) {
             updateStackPosition();
         }
 
@@ -2411,16 +2423,25 @@
                         /* notificationStackScrollLayout= */ this, mMaxDisplayedNotifications,
                         shelfIntrinsicHeight);
         mIntrinsicContentHeight = height;
-        mScrollViewFields.sendStackHeight(height + footerIntrinsicHeight);
 
         // The topPadding can be bigger than the regular padding when qs is expanded, in that
         // state the maxPanelHeight and the contentHeight should be bigger
         mContentHeight =
                 (int) (height + Math.max(mIntrinsicPadding, getTopPadding()) + mBottomPadding);
+        mScrollViewFields.setIntrinsicStackHeight(
+                (int) (mIntrinsicPadding + mIntrinsicContentHeight + footerIntrinsicHeight
+                        + mBottomPadding));
         updateScrollability();
         clampScrollPosition();
         updateStackPosition();
         mAmbientState.setContentHeight(mContentHeight);
+
+        notifyStackHeightChangedListeners();
+    }
+
+    @Override
+    public int getIntrinsicStackHeight() {
+        return mScrollViewFields.getIntrinsicStackHeight();
     }
 
     /**
@@ -3151,6 +3172,11 @@
                 type = row.wasJustClicked()
                         ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
                         : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
+                if (NotificationHeadsUpCycling.isEnabled()) {
+                    if (mStackScrollAlgorithm.isCyclingOut(row, mAmbientState)) {
+                        type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_OUT;
+                    }
+                }
                 if (row.isChildInGroup()) {
                     // We can otherwise get stuck in there if it was just isolated
                     row.setHeadsUpAnimatingAway(false);
@@ -3171,6 +3197,11 @@
                     if (pinnedAndClosed || shouldHunAppearFromTheBottom) {
                         // Our custom add animation
                         type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
+                        if (NotificationHeadsUpCycling.isEnabled()) {
+                            if (mStackScrollAlgorithm.isCyclingIn(row, mAmbientState)) {
+                                type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_IN;
+                            }
+                        }
                     } else {
                         // Normal add animation
                         type = AnimationEvent.ANIMATION_TYPE_ADD;
@@ -3402,14 +3433,21 @@
             int action = ev.getActionMasked();
             boolean isUpOrCancel = action == ACTION_UP || action == ACTION_CANCEL;
             if (mSendingTouchesToSceneFramework) {
-                mController.sendTouchToSceneFramework(ev);
+                MotionEvent adjustedEvent = MotionEvent.obtain(ev);
+                adjustedEvent.setLocation(ev.getRawX(), ev.getRawY());
+                mController.sendTouchToSceneFramework(adjustedEvent);
+                mScrollViewFields.sendCurrentGestureOverscroll(
+                        getExpandedInThisMotion() && !isUpOrCancel);
+                adjustedEvent.recycle();
             } else if (!isUpOrCancel) {
                 // if this is the first touch being sent to the scene framework,
                 // convert it into a synthetic DOWN event.
                 mSendingTouchesToSceneFramework = true;
                 MotionEvent downEvent = MotionEvent.obtain(ev);
                 downEvent.setAction(MotionEvent.ACTION_DOWN);
+                downEvent.setLocation(ev.getRawX(), ev.getRawY());
                 mController.sendTouchToSceneFramework(downEvent);
+                mScrollViewFields.sendCurrentGestureOverscroll(getExpandedInThisMotion());
                 downEvent.recycle();
             }
 
@@ -3428,6 +3466,14 @@
         downEvent.recycle();
     }
 
+    // Only when scene container is enabled, mark that we are being dragged so that we start
+    // dispatching the rest of the gesture to scene container.
+    void startOverscrollAfterExpanding() {
+        SceneContainerFlag.isUnexpectedlyInLegacyMode();
+        getExpandHelper().finishExpanding();
+        setIsBeingDragged(true);
+    }
+
     @Override
     public boolean onGenericMotionEvent(MotionEvent event) {
         if (!isScrollingEnabled()
@@ -5545,6 +5591,11 @@
         return mExpandingNotification;
     }
 
+    @VisibleForTesting
+    void setExpandingNotification(boolean isExpanding) {
+        mExpandingNotification = isExpanding;
+    }
+
     boolean getDisallowScrollingInThisMotion() {
         return mDisallowScrollingInThisMotion;
     }
@@ -5557,6 +5608,11 @@
         return mExpandedInThisMotion;
     }
 
+    @VisibleForTesting
+    void setExpandedInThisMotion(boolean expandedInThisMotion) {
+        mExpandedInThisMotion = expandedInThisMotion;
+    }
+
     boolean getDisallowDismissInThisMotion() {
         return mDisallowDismissInThisMotion;
     }
@@ -6121,6 +6177,22 @@
                         .animateTopInset()
                         .animateY()
                         .animateZ(),
+
+                // ANIMATION_TYPE_HEADS_UP_CYCLING_OUT
+                new AnimationFilter()
+                        .animateHeight()
+                        .animateTopInset()
+                        .animateY()
+                        .animateZ()
+                        .hasDelays(),
+
+                // ANIMATION_TYPE_HEADS_UP_CYCLING_IN
+                new AnimationFilter()
+                        .animateHeight()
+                        .animateTopInset()
+                        .animateY()
+                        .animateZ()
+                        .hasDelays(),
         };
 
         static int[] LENGTHS = new int[]{
@@ -6172,6 +6244,12 @@
 
                 // ANIMATION_TYPE_EVERYTHING
                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
+
+                // ANIMATION_TYPE_HEADS_UP_CYCLING_OUT
+                StackStateAnimator.ANIMATION_DURATION_HEADS_UP_CYCLING,
+
+                // ANIMATION_TYPE_HEADS_UP_CYCLING_IN
+                StackStateAnimator.ANIMATION_DURATION_HEADS_UP_CYCLING,
         };
 
         static final int ANIMATION_TYPE_ADD = 0;
@@ -6190,6 +6268,8 @@
         static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 13;
         static final int ANIMATION_TYPE_HEADS_UP_OTHER = 14;
         static final int ANIMATION_TYPE_EVERYTHING = 15;
+        static final int ANIMATION_TYPE_HEADS_UP_CYCLING_OUT = 16;
+        static final int ANIMATION_TYPE_HEADS_UP_CYCLING_IN = 17;
 
         final long eventStartTime;
         final ExpandableView mChangingView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 5bb3f42..6a3055f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -23,7 +23,7 @@
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
 import static com.android.server.notification.Flags.screenshareNotificationHiding;
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
-import static com.android.systemui.Flags.nsslFalsingFix;
+import static com.android.systemui.Flags.confineNotificationTouchToViewWidth;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener;
@@ -206,6 +206,7 @@
     private final SeenNotificationsInteractor mSeenNotificationsInteractor;
     private final KeyguardTransitionRepository mKeyguardTransitionRepo;
     private NotificationStackScrollLayout mView;
+    private TouchHandler mTouchHandler;
     private NotificationSwipeHelper mSwipeHelper;
     @Nullable
     private Boolean mHistoryEnabled;
@@ -597,7 +598,7 @@
                             ev.getY(),
                             true /* requireMinHeight */,
                             false /* ignoreDecors */,
-                            true /* ignoreWidth */);
+                            !confineNotificationTouchToViewWidth() /* ignoreWidth */);
                     if (child instanceof ExpandableNotificationRow row) {
                         ExpandableNotificationRow parent = row.getNotificationParent();
                         if (parent != null && parent.areChildrenExpanded()
@@ -807,7 +808,8 @@
         mView.setStackStateLogger(mStackStateLogger);
         mView.setController(this);
         mView.setLogger(mLogger);
-        mView.setTouchHandler(new TouchHandler());
+        mTouchHandler = new TouchHandler();
+        mView.setTouchHandler(mTouchHandler);
         mView.setResetUserExpandedStatesRunnable(mNotificationsController::resetUserExpandedStates);
         mView.setActivityStarter(mActivityStarter);
         mView.setClearAllAnimationListener(this::onAnimationEnd);
@@ -1793,6 +1795,11 @@
         }
     }
 
+    @VisibleForTesting
+    TouchHandler getTouchHandler() {
+        return mTouchHandler;
+    }
+
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("mMaxAlphaFromView=" + mMaxAlphaFromView);
@@ -2043,37 +2050,30 @@
                 expandingNotification = mView.isExpandingNotification();
                 if (mView.getExpandedInThisMotion() && !expandingNotification && wasExpandingBefore
                         && !mView.getDisallowScrollingInThisMotion()) {
-                    mView.dispatchDownEventToScroller(ev);
+                    // We need to dispatch the overscroll differently when Scene Container is on,
+                    // since NSSL no longer controls its own scroll.
+                    if (SceneContainerFlag.isEnabled() && !isCancelOrUp) {
+                        mView.startOverscrollAfterExpanding();
+                        return true;
+                    } else {
+                        mView.dispatchDownEventToScroller(ev);
+                    }
                 }
             }
             boolean horizontalSwipeWantsIt = false;
             boolean scrollerWantsIt = false;
-            if (nsslFalsingFix() || MigrateClocksToBlueprint.isEnabled()) {
-                // Reverse the order relative to the else statement. onScrollTouch will reset on an
-                // UP event, causing horizontalSwipeWantsIt to be set to true on vertical swipes.
-                if (mLongPressedView == null && !mView.isBeingDragged()
-                        && !expandingNotification
-                        && !mView.getExpandedInThisMotion()
-                        && !onlyScrollingInThisMotion
-                        && !mView.getDisallowDismissInThisMotion()) {
-                    horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
-                }
-                if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping()
-                        && !expandingNotification && !mView.getDisallowScrollingInThisMotion()) {
-                    scrollerWantsIt = mView.onScrollTouch(ev);
-                }
-            } else {
-                if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping()
-                        && !expandingNotification && !mView.getDisallowScrollingInThisMotion()) {
-                    scrollerWantsIt = mView.onScrollTouch(ev);
-                }
-                if (mLongPressedView == null && !mView.isBeingDragged()
-                        && !expandingNotification
-                        && !mView.getExpandedInThisMotion()
-                        && !onlyScrollingInThisMotion
-                        && !mView.getDisallowDismissInThisMotion()) {
-                    horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
-                }
+            // NOTE: the order of these is important. If reversed, onScrollTouch will reset on an
+            // UP event, causing horizontalSwipeWantsIt to be set to true on vertical swipes.
+            if (mLongPressedView == null && !mView.isBeingDragged()
+                    && !expandingNotification
+                    && !mView.getExpandedInThisMotion()
+                    && !onlyScrollingInThisMotion
+                    && !mView.getDisallowDismissInThisMotion()) {
+                horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
+            }
+            if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping()
+                    && !expandingNotification && !mView.getDisallowScrollingInThisMotion()) {
+                scrollerWantsIt = mView.onScrollTouch(ev);
             }
 
             // Check if we need to clear any snooze leavebehinds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 5bd4c75..4b0b1e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -17,11 +17,9 @@
 package com.android.systemui.statusbar.notification.stack
 
 import android.content.res.Resources
-import android.os.SystemProperties
 import android.util.Log
 import android.view.View.GONE
 import androidx.annotation.VisibleForTesting
-import com.android.systemui.Flags.notificationMinimalismPrototype
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
@@ -31,6 +29,7 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
 import com.android.systemui.statusbar.policy.SplitShadeStateController
 import com.android.systemui.util.Compile
 import com.android.systemui.util.children
@@ -73,6 +72,9 @@
      */
     private var maxNotificationsExcludesMedia = false
 
+    /** Whether we allow keyguard to show less important notifications above the shelf. */
+    private var limitLockScreenToImportant = false
+
     /** Minimum space between two notifications, see [calculateGapAndDividerHeight]. */
     private var dividerHeight by notNull<Float>()
 
@@ -86,6 +88,14 @@
         updateResources()
     }
 
+    private fun allowedByPolicy(stackHeight: StackHeight): Boolean =
+        if (limitLockScreenToImportant && stackHeight.includesLessImportantNotification) {
+            log { "\tallowedByPolicy = false" }
+            false
+        } else {
+            true
+        }
+
     /**
      * Returns whether notifications and (shelf if visible) can fit in total space available.
      * [shelfSpace] is extra vertical space allowed for the shelf to overlap the lock icon.
@@ -184,11 +194,12 @@
         log { "\tGet maxNotifWithoutSavingSpace ---" }
         val maxNotifWithoutSavingSpace =
             stackHeightSequence.lastIndexWhile { heightResult ->
-                canStackFitInSpace(
-                    heightResult,
-                    notifSpace = notifSpace,
-                    shelfSpace = shelfSpace
-                ) == FitResult.FIT
+                allowedByPolicy(heightResult) &&
+                    canStackFitInSpace(
+                        heightResult,
+                        notifSpace = notifSpace,
+                        shelfSpace = shelfSpace
+                    ) == FitResult.FIT
             }
 
         // How many notifications we can show at heightWithoutLockscreenConstraints
@@ -213,11 +224,12 @@
             saveSpaceOnLockscreen = true
             maxNotifications =
                 stackHeightSequence.lastIndexWhile { heightResult ->
-                    canStackFitInSpace(
-                        heightResult,
-                        notifSpace = notifSpace,
-                        shelfSpace = shelfSpace
-                    ) != FitResult.NO_FIT
+                    allowedByPolicy(heightResult) &&
+                        canStackFitInSpace(
+                            heightResult,
+                            notifSpace = notifSpace,
+                            shelfSpace = shelfSpace
+                        ) != FitResult.NO_FIT
                 }
             log { "\t--- maxNotifications=$maxNotifications" }
         }
@@ -319,7 +331,10 @@
 
         // Float height of shelf (0 if shelf is not showing), and space before the shelf that
         // changes during the lockscreen <=> full shade transition.
-        val shelfHeightWithSpaceBefore: Float
+        val shelfHeightWithSpaceBefore: Float,
+
+        /** Whether this stack height includes less at least one important notification. */
+        val includesLessImportantNotification: Boolean
     )
 
     private fun computeHeightPerNotificationLimit(
@@ -332,12 +347,15 @@
         var previous: ExpandableView? = null
         val onLockscreen = onLockscreen()
 
+        var includesLessImportantNotification = false
+
         // Only shelf. This should never happen, since we allow 1 view minimum (EmptyViewState).
         yield(
             StackHeight(
                 notifsHeight = 0f,
                 notifsHeightSavingSpace = 0f,
-                shelfHeightWithSpaceBefore = shelfHeight
+                shelfHeightWithSpaceBefore = shelfHeight,
+                includesLessImportantNotification = includesLessImportantNotification,
             )
         )
 
@@ -363,6 +381,19 @@
                     spaceBeforeShelf + shelfHeight
                 }
 
+            if (limitLockScreenToImportant && !includesLessImportantNotification) {
+                val bucket = (currentNotification as? ExpandableNotificationRow)?.entry?.bucket
+                includesLessImportantNotification =
+                    when (bucket) {
+                        null,
+                        BUCKET_MEDIA_CONTROLS,
+                        BUCKET_HEADS_UP,
+                        BUCKET_FOREGROUND_SERVICE,
+                        BUCKET_PRIORITY_PEOPLE -> false
+                        else -> true
+                    }
+            }
+
             log {
                 "\tcomputeHeightPerNotificationLimit i=$i notifs=$notifications " +
                     "notifsHeightSavingSpace=$notifsWithCollapsedHun" +
@@ -372,7 +403,8 @@
                 StackHeight(
                     notifsHeight = notifications,
                     notifsHeightSavingSpace = notifsWithCollapsedHun,
-                    shelfHeightWithSpaceBefore = shelfWithSpaceBefore
+                    shelfHeightWithSpaceBefore = shelfWithSpaceBefore,
+                    includesLessImportantNotification = includesLessImportantNotification,
                 )
             )
         }
@@ -381,16 +413,18 @@
     fun updateResources() {
         maxKeyguardNotifications =
             infiniteIfNegative(
-                if (notificationMinimalismPrototype()) {
-                    SystemProperties.getInt(
-                        "persist.notification_minimalism_prototype.lock_screen_max_notifs",
-                        1
-                    )
+                if (NotificationMinimalismPrototype.V1.isEnabled) {
+                    NotificationMinimalismPrototype.V1.maxNotifs
+                } else if (NotificationMinimalismPrototype.V2.isEnabled) {
+                    1
                 } else {
                     resources.getInteger(R.integer.keyguard_max_notification_count)
                 }
             )
-        maxNotificationsExcludesMedia = notificationMinimalismPrototype()
+        maxNotificationsExcludesMedia =
+            NotificationMinimalismPrototype.V1.isEnabled ||
+                NotificationMinimalismPrototype.V2.isEnabled
+        limitLockScreenToImportant = NotificationMinimalismPrototype.V2.isEnabled
 
         dividerHeight =
             max(1f, resources.getDimensionPixelSize(R.dimen.notification_divider_height).toFloat())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
index edac5ed..6afcf37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
@@ -45,16 +45,22 @@
     var isScrolledToTop: Boolean = true
 
     /**
+     * Height in view pixels at which the Notification Stack would like to be laid out, including
+     * Notification rows, paddings the Shelf and the Footer.
+     */
+    var intrinsicStackHeight: Int = 0
+
+    /**
      * When internal NSSL expansion requires the stack to be scrolled (e.g. to keep an expanding
      * notification in view), that scroll amount can be sent here and it will be handled by the
      * placeholder
      */
     var syntheticScrollConsumer: Consumer<Float>? = null
     /**
-     * Any time the stack height is recalculated, it should be updated here to be used by the
-     * placeholder
+     * When a gesture is consumed internally by NSSL but needs to be handled by other elements (such
+     * as the notif scrim) as overscroll, we can notify the placeholder through here.
      */
-    var stackHeightConsumer: Consumer<Float>? = null
+    var currentGestureOverscrollConsumer: Consumer<Boolean>? = null
     /**
      * Any time the heads up height is recalculated, it should be updated here to be used by the
      * placeholder
@@ -64,8 +70,9 @@
     /** send the [syntheticScroll] to the [syntheticScrollConsumer], if present. */
     fun sendSyntheticScroll(syntheticScroll: Float) =
         syntheticScrollConsumer?.accept(syntheticScroll)
-    /** send the [stackHeight] to the [stackHeightConsumer], if present. */
-    fun sendStackHeight(stackHeight: Float) = stackHeightConsumer?.accept(stackHeight)
+    /** send [isCurrentGestureOverscroll] to the [currentGestureOverscrollConsumer], if present. */
+    fun sendCurrentGestureOverscroll(isCurrentGestureOverscroll: Boolean) =
+        currentGestureOverscrollConsumer?.accept(isCurrentGestureOverscroll)
     /** send the [headsUpHeight] to the [headsUpHeightConsumer], if present. */
     fun sendHeadsUpHeight(headsUpHeight: Float) = headsUpHeightConsumer?.accept(headsUpHeight)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index e980794..0fcfc4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -38,6 +38,7 @@
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
 import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
 
 import java.util.ArrayList;
@@ -75,6 +76,7 @@
     private float mSmallCornerRadius;
     private float mLargeCornerRadius;
     private int mHeadsUpAppearHeightBottom;
+    private int mHeadsUpCyclingPadding;
 
     public StackScrollAlgorithm(
             Context context,
@@ -99,6 +101,8 @@
                 R.dimen.heads_up_status_bar_padding);
         mHeadsUpAppearStartAboveScreen = res.getDimensionPixelSize(
                 R.dimen.heads_up_appear_y_above_screen);
+        mHeadsUpCyclingPadding = context.getResources()
+                .getDimensionPixelSize(R.dimen.heads_up_cycling_padding);
         mPinnedZTranslationExtra = res.getDimensionPixelSize(
                 R.dimen.heads_up_pinned_elevation);
         mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
@@ -169,6 +173,14 @@
                 }
             }
 
+            // On the final call to {@link #resetViewState}, the alpha is set back to 1f but
+            // ambientState.isExpansionChanging() is now false. This causes a flicker on the
+            // EmptyShadeView after the shade is collapsed. Make sure the empty shade view
+            // isn't visible unless the shade is expanded.
+            if (view instanceof EmptyShadeView && ambientState.getExpansionFraction() == 0f) {
+                viewState.setAlpha(0f);
+            }
+
             // For EmptyShadeView if on keyguard, we need to control the alpha to create
             // a nice transition when the user is dragging down the notification panel.
             if (view instanceof EmptyShadeView && ambientState.isOnKeyguard()) {
@@ -340,7 +352,8 @@
                     && !firstHeadsUp
                     && (isHeadsUp || child.isHeadsUpAnimatingAway())
                     && newNotificationEnd > firstHeadsUpEnd
-                    && !ambientState.isShadeExpanded()) {
+                    && !ambientState.isShadeExpanded()
+                    && !skipClipBottomForCycling(child, ambientState)) {
                 // The bottom of this view is peeking out from under the previous view.
                 // Clip the part that is peeking out.
                 float overlapAmount = newNotificationEnd - firstHeadsUpEnd;
@@ -362,6 +375,44 @@
         }
     }
 
+    /**
+     * @return Should we skip clipping the bottom clipping when new hun has lower bottom line for
+     *         the hun cycling animation.
+     */
+    private boolean skipClipBottomForCycling(ExpandableView view, AmbientState ambientState) {
+        if (!NotificationHeadsUpCycling.isEnabled()) return false;
+        if (!isCyclingOut(view, ambientState)) return false;
+        // skip bottom clipping if we animate the bottom line
+        return NotificationHeadsUpCycling.getAnimateTallToShort();
+    }
+
+    /**
+     * Whether the view is the hun that is cycling out by the notification avalanche.
+     */
+    public boolean isCyclingOut(ExpandableView view, AmbientState ambientState) {
+        if (!NotificationHeadsUpCycling.isEnabled()) return false;
+        if (!(view instanceof ExpandableNotificationRow)) return false;
+        return isCyclingOut((ExpandableNotificationRow) view, ambientState);
+    }
+
+    /**
+     * Whether the row is the hun that is cycling out by the notification avalanche.
+     */
+    public boolean isCyclingOut(ExpandableNotificationRow row, AmbientState ambientState) {
+        if (!NotificationHeadsUpCycling.isEnabled()) return false;
+        String cyclingOutKey = ambientState.getAvalanchePreviousHunKey();
+        return row.getEntry().getKey().equals(cyclingOutKey);
+    }
+
+    /**
+     * Whether the row is the hun that is cycling in by the notification avalanche.
+     */
+    public boolean isCyclingIn(ExpandableNotificationRow row, AmbientState ambientState) {
+        if (!NotificationHeadsUpCycling.isEnabled()) return false;
+        String cyclingInKey = ambientState.getAvalancheShowingHunKey();
+        return row.getEntry().getKey().equals(cyclingInKey);
+    }
+
     /** Updates the dimmed and hiding sensitive states of the children. */
     private void updateDimmedAndHideSensitive(AmbientState ambientState,
             StackScrollAlgorithmState algorithmState) {
@@ -791,6 +842,7 @@
         }
 
         ExpandableNotificationRow topHeadsUpEntry = null;
+        int cyclingInHunHeight = -1;
         for (int i = 0; i < childCount; i++) {
             View child = algorithmState.visibleChildren.get(i);
             if (!(child instanceof ExpandableNotificationRow row)) {
@@ -831,6 +883,13 @@
                 childState.setYTranslation(
                         Math.max(childState.getYTranslation(), headsUpTranslation));
                 childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
+                if (NotificationHeadsUpCycling.isEnabled()) {
+                    if (isCyclingIn(row, ambientState)) {
+                        if (cyclingInHunHeight == -1) {
+                            cyclingInHunHeight = childState.height;
+                        }
+                    }
+                }
                 childState.hidden = false;
                 ExpandableViewState topState =
                         topHeadsUpEntry == null ? null : topHeadsUpEntry.getViewState();
@@ -852,6 +911,26 @@
                 }
             }
             if (row.isHeadsUpAnimatingAway()) {
+                if (NotificationHeadsUpCycling.isEnabled() && isCyclingOut(row, ambientState)) {
+                    // If the two HUNs in the cycling animation have different heights, we need
+                    // an extra y translation to align the animation.
+                    int extraTranslation;
+                    if (NotificationHeadsUpCycling.getAnimateTallToShort()) {
+                        if (cyclingInHunHeight > 0) {
+                            extraTranslation = cyclingInHunHeight - childState.height;
+                        } else {
+                            extraTranslation = 0;
+                        }
+                    } else {
+                        extraTranslation = cyclingInHunHeight >= childState.height
+                                ? cyclingInHunHeight - childState.height : 0;
+                    }
+                    extraTranslation += mHeadsUpCyclingPadding;
+                    float inSpaceTranslation = Math.max(childState.getYTranslation(),
+                            headsUpTranslation);
+                    childState.setYTranslation(inSpaceTranslation + extraTranslation);
+                    cyclingInHunHeight = -1;
+                } else
                 if (NotificationsImprovedHunAnimation.isEnabled() && !ambientState.isDozing()) {
                     if (shouldHunAppearFromBottom(ambientState, childState)) {
                         // move to the bottom of the screen
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 5963d35..5dc5449 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -17,6 +17,8 @@
 package com.android.systemui.statusbar.notification.stack;
 
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_IN;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_OUT;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK;
 
@@ -57,6 +59,7 @@
     public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
     public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400;
     public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 400;
+    public static final int ANIMATION_DURATION_HEADS_UP_CYCLING = 400;
     public static final int ANIMATION_DURATION_FOLD_TO_AOD =
             AnimatableClockView.ANIMATION_DURATION_FOLD_TO_AOD;
     public static final int ANIMATION_DURATION_PRIORITY_CHANGE = 500;
@@ -68,6 +71,8 @@
 
     @VisibleForTesting int mGoToFullShadeAppearingTranslation;
     @VisibleForTesting float mHeadsUpAppearStartAboveScreen;
+    // Padding between the old and new heads up notifications for the hun cycling animation
+    private float mHeadsUpCyclingPadding;
     private final ExpandableViewState mTmpState = new ExpandableViewState();
     private final AnimationProperties mAnimationProperties;
     public NotificationStackScrollLayout mHostLayout;
@@ -125,6 +130,8 @@
                         R.dimen.go_to_full_shade_appearing_translation);
         mHeadsUpAppearStartAboveScreen = context.getResources()
                 .getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen);
+        mHeadsUpCyclingPadding = context.getResources()
+                .getDimensionPixelSize(R.dimen.heads_up_cycling_padding);
     }
 
     protected void setLogger(StackStateLogger logger) {
@@ -449,7 +456,8 @@
                 }
                 changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
                         0 /* delay */, translationDirection, false /* isHeadsUpAppear */,
-                        startAnimation, postAnimation, getGlobalAnimationFinishedListener());
+                        startAnimation, postAnimation, getGlobalAnimationFinishedListener(),
+                        ExpandableView.ClipSide.BOTTOM);
                 needsCustomAnimation = true;
             } else if (event.animationType ==
                 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
@@ -464,6 +472,27 @@
                     .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) {
                 ExpandableNotificationRow row = (ExpandableNotificationRow) event.mChangingView;
                 row.prepareExpansionChanged();
+            } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_CYCLING_IN) {
+                mHeadsUpAppearChildren.add(changingView);
+
+                mTmpState.copyFrom(changingView.getViewState());
+                mTmpState.setYTranslation(changingView.getViewState().getYTranslation()
+                        + getHeadsUpCyclingInYTranslationStart(event.headsUpFromBottom));
+                mTmpState.applyToView(changingView);
+
+                // TODO(b/339519404): use a different interpolator
+                Runnable onAnimationEnd = null;
+                if (loggable) {
+                    // This only captures HEADS_UP_APPEAR animations, but HUNs can appear with
+                    // normal ADD animations, which would not be logged here.
+                    String finalKey = key;
+                    mLogger.logHUNViewAppearing(key);
+                    onAnimationEnd = () -> {
+                        mLogger.appearAnimationEnded(finalKey);
+                    };
+                }
+                changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_CYCLING,
+                        /* isHeadsUpAppear= */ true, onAnimationEnd);
             } else if (NotificationsImprovedHunAnimation.isEnabled()
                     && (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR)) {
                 mHeadsUpAppearChildren.add(changingView);
@@ -486,6 +515,87 @@
                 }
                 changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR,
                         /* isHeadsUpAppear= */ true, onAnimationEnd);
+            } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_CYCLING_OUT) {
+                mHeadsUpDisappearChildren.add(changingView);
+                Runnable endRunnable = null;
+                mTmpState.copyFrom(changingView.getViewState());
+
+                if (changingView.getParent() == null) {
+                    // This notification was actually removed, so we need to add it
+                    // transiently
+                    mHostLayout.addTransientView(changingView, 0);
+                    changingView.setTransientContainer(mHostLayout);
+                    // TODO(b/316404716): remove the hard-coded height
+                    // StackScrollAlgorithm cannot find this view because it has been removed
+                    // from the NSSL. To correctly translate the view to the top or bottom of
+                    // the screen (where it animated from), we need to update its translation.
+                    mTmpState.setYTranslation(
+                            mTmpState.getYTranslation() + 10
+                    );
+                    endRunnable = changingView::removeFromTransientContainer;
+                }
+
+                boolean needsAnimation = true;
+                if (changingView instanceof ExpandableNotificationRow) {
+                    ExpandableNotificationRow row =
+                            (ExpandableNotificationRow) changingView;
+                    if (row.isDismissed()) {
+                        needsAnimation = false;
+                    }
+                }
+                if (needsAnimation) {
+                    // We need to add the global animation listener, since once no animations are
+                    // running anymore, the panel will instantly hide itself. We need to wait until
+                    // the animation is fully finished for this though.
+                    final Runnable tmpEndRunnable = endRunnable;
+                    Runnable postAnimation;
+                    Runnable startAnimation;
+                    if (loggable) {
+                        String finalKey1 = key;
+                        final boolean finalIsHeadsUp = isHeadsUp;
+                        final String type = "ANIMATION_TYPE_HEADS_UP_CYCLING_OUT";
+                        startAnimation = () -> {
+                            mLogger.animationStart(finalKey1, type, finalIsHeadsUp);
+                            changingView.setInRemovalAnimation(true);
+                        };
+                        postAnimation = () -> {
+                            mLogger.animationEnd(finalKey1, type, finalIsHeadsUp);
+                            changingView.setInRemovalAnimation(false);
+                            if (tmpEndRunnable != null) {
+                                tmpEndRunnable.run();
+                            }
+
+                        };
+                    } else {
+                        postAnimation = () -> {
+                            changingView.setInRemovalAnimation(false);
+                            if (tmpEndRunnable != null) {
+                                tmpEndRunnable.run();
+                            }
+                        };
+                        startAnimation = () -> {
+                            changingView.setInRemovalAnimation(true);
+                        };
+                    }
+                    long removeAnimationDelay = changingView.performRemoveAnimation(
+                            ANIMATION_DURATION_HEADS_UP_CYCLING,
+                            /* delay= */ 0,
+                            // It's a shame that translationDirection isn't where we do the y
+                            // translation, the actual translation is in StackScrollAlgorithm.
+                            /* translationDirection= */ 0.0f,
+                            /* isHeadsUpAnimation= */ true,
+                            startAnimation, postAnimation,
+                            getGlobalAnimationFinishedListener(), ExpandableView.ClipSide.TOP);
+                    mAnimationProperties.delay += removeAnimationDelay;
+                    mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_CYCLING;
+                    mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
+                            Interpolators.LINEAR);
+                    mAnimationProperties.getAnimationFilter().animateY = true;
+                    mTmpState.animateTo(changingView, mAnimationProperties);
+                } else if (endRunnable != null) {
+                    endRunnable.run();
+                }
+                needsCustomAnimation |= needsAnimation;
             } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR) {
                 NotificationsImprovedHunAnimation.assertInLegacyMode();
                 // This item is added, initialize its properties.
@@ -565,21 +675,21 @@
                             }
                         };
                     } else {
+                        startAnimation = () -> {
+                            changingView.setInRemovalAnimation(true);
+                        };
                         postAnimation = () -> {
                             changingView.setInRemovalAnimation(false);
                             if (tmpEndRunnable != null) {
                                 tmpEndRunnable.run();
                             }
                         };
-                        startAnimation = () -> {
-                            changingView.setInRemovalAnimation(true);
-                        };
                     }
                     long removeAnimationDelay = changingView.performRemoveAnimation(
                             ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
                             0, 0.0f, true /* isHeadsUpAppear */,
                             startAnimation, postAnimation,
-                            getGlobalAnimationFinishedListener());
+                            getGlobalAnimationFinishedListener(), ExpandableView.ClipSide.BOTTOM);
                     mAnimationProperties.delay += removeAnimationDelay;
                     if (NotificationsImprovedHunAnimation.isEnabled()) {
                         mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR;
@@ -607,6 +717,38 @@
         return -mStackTopMargin - mHeadsUpAppearStartAboveScreen;
     }
 
+    /**
+     * @param headsUpFromBottom Whether we are showing the HUNs at the bottom of the screen
+     * @return The start y translation of the HUN cycling in animation
+     */
+    private float getHeadsUpCyclingInYTranslationStart(boolean headsUpFromBottom) {
+        if (headsUpFromBottom) {
+            // start from the bottom of the screen
+            return mHeadsUpAppearHeightBottom + mHeadsUpCyclingPadding;
+        }
+        // start from the top of the screen
+        return -mHeadsUpCyclingPadding;
+    }
+
+    /**
+     * @param headsUpFromBottom Whether we are showing the HUNs at the bottom of the screen
+     * @param oldHunHeight Height of the old HUN
+     * @param newHunHeight Height of the new HUN
+     * @return The y translation target value of the HUN cycling out animation
+     */
+    private float getHeadsUpCyclingOutYTranslation(
+            boolean headsUpFromBottom,
+            int oldHunHeight,
+            int newHunHeight
+    ) {
+        final float translationDistance = mHeadsUpCyclingPadding + newHunHeight - oldHunHeight;
+        if (headsUpFromBottom) {
+            // start from the bottom of the screen
+            return mHeadsUpAppearHeightBottom - translationDistance;
+        }
+        return translationDistance;
+    }
+
     public void animateOverScrollToAmount(float targetAmount, final boolean onTop,
             final boolean isRubberbanded) {
         final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
index dacafc4..db544ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
@@ -38,16 +38,6 @@
      */
     val shadeScrimBounds = MutableStateFlow<ShadeScrimBounds?>(null)
 
-    /**
-     * The y-coordinate in px of top of the contents of the notification stack. This value can be
-     * negative, if the stack is scrolled such that its top extends beyond the top edge of the
-     * screen.
-     */
-    val stackTop = MutableStateFlow(0f)
-
-    /** the bottom-most acceptable y-position for the bottom of the stack / shelf */
-    val stackBottom = MutableStateFlow(0f)
-
     /** the y position of the top of the HUN area */
     val headsUpTop = MutableStateFlow(0f)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt
index 8a9da69..463c631 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt
@@ -27,13 +27,6 @@
 @SysUISingleton
 class NotificationViewHeightRepository @Inject constructor() {
 
-    /**
-     * The height in px of the contents of notification stack. Depending on the number of
-     * notifications, this can exceed the space available on screen to show notifications, at which
-     * point the notification stack should become scrollable.
-     */
-    val stackHeight = MutableStateFlow(0f)
-
     /** The height in px of the current heads up notification. */
     val headsUpHeight = MutableStateFlow(0f)
 
@@ -43,4 +36,10 @@
      * necessary to scroll up to keep expanding the notification.
      */
     val syntheticScroll = MutableStateFlow(0f)
+
+    /**
+     * Whether the current touch gesture is overscroll. If true, it means the NSSL has already
+     * consumed part of the gesture.
+     */
+    val isCurrentGestureOverscroll = MutableStateFlow(false)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index b8660ba..e7acbe3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -65,13 +65,6 @@
             }
             .distinctUntilChanged()
 
-    /**
-     * The height in px of the contents of notification stack. Depending on the number of
-     * notifications, this can exceed the space available on screen to show notifications, at which
-     * point the notification stack should become scrollable.
-     */
-    val stackHeight: StateFlow<Float> = viewHeightRepository.stackHeight.asStateFlow()
-
     /** The height in px of the contents of the HUN. */
     val headsUpHeight: StateFlow<Float> = viewHeightRepository.headsUpHeight.asStateFlow()
 
@@ -79,12 +72,6 @@
     val alphaForBrightnessMirror: StateFlow<Float> =
         placeholderRepository.alphaForBrightnessMirror.asStateFlow()
 
-    /** The y-coordinate in px of top of the contents of the notification stack. */
-    val stackTop: StateFlow<Float> = placeholderRepository.stackTop.asStateFlow()
-
-    /** The y-coordinate in px of bottom of the contents of the notification stack. */
-    val stackBottom: StateFlow<Float> = placeholderRepository.stackBottom.asStateFlow()
-
     /** The height of the keyguard's available space bounds */
     val constrainedAvailableSpace: StateFlow<Int> =
         placeholderRepository.constrainedAvailableSpace.asStateFlow()
@@ -105,6 +92,13 @@
      */
     val syntheticScroll: Flow<Float> = viewHeightRepository.syntheticScroll.asStateFlow()
 
+    /**
+     * Whether the current touch gesture is overscroll. If true, it means the NSSL has already
+     * consumed part of the gesture.
+     */
+    val isCurrentGestureOverscroll: Flow<Boolean> =
+        viewHeightRepository.isCurrentGestureOverscroll.asStateFlow()
+
     /** Sets the alpha to apply to the NSSL for the brightness mirror */
     fun setAlphaForBrightnessMirror(alpha: Float) {
         placeholderRepository.alphaForBrightnessMirror.value = alpha
@@ -116,26 +110,11 @@
         placeholderRepository.shadeScrimBounds.value = bounds
     }
 
-    /** Sets the height of the contents of the notification stack. */
-    fun setStackHeight(height: Float) {
-        viewHeightRepository.stackHeight.value = height
-    }
-
     /** Sets the height of heads up notification. */
     fun setHeadsUpHeight(height: Float) {
         viewHeightRepository.headsUpHeight.value = height
     }
 
-    /** Sets the y-coord in px of the top of the contents of the notification stack. */
-    fun setStackTop(stackTop: Float) {
-        placeholderRepository.stackTop.value = stackTop
-    }
-
-    /** Sets the y-coord in px of the bottom of the contents of the notification stack. */
-    fun setStackBottom(stackBottom: Float) {
-        placeholderRepository.stackBottom.value = stackBottom
-    }
-
     /** Sets whether the notification stack is scrolled to the top. */
     fun setScrolledToTop(scrolledToTop: Boolean) {
         placeholderRepository.scrolledToTop.value = scrolledToTop
@@ -146,6 +125,11 @@
         viewHeightRepository.syntheticScroll.value = delta
     }
 
+    /** Sets whether the current touch gesture is overscroll. */
+    fun setCurrentGestureOverscroll(isOverscroll: Boolean) {
+        viewHeightRepository.isCurrentGestureOverscroll.value = isOverscroll
+    }
+
     fun setConstrainedAvailableSpace(height: Int) {
         placeholderRepository.constrainedAvailableSpace.value = height
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
index 20e8cac..9b21fa9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
@@ -29,11 +29,10 @@
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.debounce
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
@@ -54,9 +53,9 @@
     private val _topPosition = MutableStateFlow(0f)
     val topPosition = _topPosition.asStateFlow()
 
-    private val _notificationStackChanged = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+    private val _notificationStackChanged = MutableStateFlow(0L)
     /** An internal modification was made to notifications */
-    val notificationStackChanged = _notificationStackChanged.asSharedFlow()
+    val notificationStackChanged = _notificationStackChanged.debounce(20L)
 
     val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
         configurationRepository.onAnyConfigurationChange
@@ -113,7 +112,7 @@
 
     /** An internal modification was made to notifications */
     fun notificationStackChanged() {
-        _notificationStackChanged.tryEmit(Unit)
+        _notificationStackChanged.value = _notificationStackChanged.value + 1
     }
 
     data class ConfigurationBasedDimensions(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index a56384d..14b882f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -25,6 +25,13 @@
  * notification stack, but is otherwise agnostic to the content.
  */
 interface NotificationScrollView {
+
+    /**
+     * Height in view pixels at which the Notification Stack would like to be laid out, including
+     * Notification rows, paddings the Shelf and the Footer.
+     */
+    val intrinsicStackHeight: Int
+
     /**
      * Since this is an interface rather than a literal View, this provides cast-like access to the
      * underlying view.
@@ -51,8 +58,8 @@
 
     /** Set a consumer for synthetic scroll events */
     fun setSyntheticScrollConsumer(consumer: Consumer<Float>?)
-    /** Set a consumer for stack height changed events */
-    fun setStackHeightConsumer(consumer: Consumer<Float>?)
+    /** Set a consumer for current gesture overscroll events */
+    fun setCurrentGestureOverscrollConsumer(consumer: Consumer<Boolean>?)
     /** Set a consumer for heads up height changed events */
     fun setHeadsUpHeightConsumer(consumer: Consumer<Float>?)
 
@@ -64,4 +71,10 @@
 
     /** Sets whether the view is displayed in doze mode. */
     fun setDozing(dozing: Boolean)
+
+    /** Sets a listener to be notified, when the stack height might have changed. */
+    fun addStackHeightChangedListener(runnable: Runnable)
+
+    /** @see addStackHeightChangedListener */
+    fun removeStackHeightChangedListener(runnable: Runnable)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index 4476d87..622d8e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -79,8 +79,6 @@
         }
 
         launch { viewModel.maxAlpha.collect { view.setMaxAlpha(it) } }
-        launch { viewModel.stackTop.collect { view.setStackTop(it) } }
-        launch { viewModel.stackBottom.collect { view.setStackBottom(it) } }
         launch { viewModel.scrolledToTop.collect { view.setScrolledToTop(it) } }
         launch { viewModel.headsUpTop.collect { view.setHeadsUpTop(it) } }
         launch { viewModel.expandFraction.collect { view.setExpandFraction(it.coerceIn(0f, 1f)) } }
@@ -89,11 +87,11 @@
 
         launchAndDispose {
             view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer)
-            view.setStackHeightConsumer(viewModel.stackHeightConsumer)
+            view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer)
             view.setHeadsUpHeightConsumer(viewModel.headsUpHeightConsumer)
             DisposableHandle {
                 view.setSyntheticScrollConsumer(null)
-                view.setStackHeightConsumer(null)
+                view.setCurrentGestureOverscrollConsumer(null)
                 view.setHeadsUpHeightConsumer(null)
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 8b1b93bf..6137381 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -130,10 +130,6 @@
     val maxAlpha: Flow<Float> =
         stackAppearanceInteractor.alphaForBrightnessMirror.dumpValue("maxAlpha")
 
-    /** The y-coordinate in px of top of the contents of the notification stack. */
-    val stackTop: Flow<Float> = stackAppearanceInteractor.stackTop.dumpValue("stackTop")
-    /** The y-coordinate in px of bottom of the contents of the notification stack. */
-    val stackBottom: Flow<Float> = stackAppearanceInteractor.stackBottom.dumpValue("stackBottom")
     /**
      * Whether the notification stack is scrolled to the top; i.e., it cannot be scrolled down any
      * further.
@@ -145,8 +141,12 @@
 
     /** Receives the amount (px) that the stack should scroll due to internal expansion. */
     val syntheticScrollConsumer: (Float) -> Unit = stackAppearanceInteractor::setSyntheticScroll
-    /** Receives the height of the contents of the notification stack. */
-    val stackHeightConsumer: (Float) -> Unit = stackAppearanceInteractor::setStackHeight
+    /**
+     * Receives whether the current touch gesture is overscroll as it has already been consumed by
+     * the stack.
+     */
+    val currentGestureOverscrollConsumer: (Boolean) -> Unit =
+        stackAppearanceInteractor::setCurrentGestureOverscroll
     /** Receives the height of the heads up notification. */
     val headsUpHeightConsumer: (Float) -> Unit = stackAppearanceInteractor::setHeadsUpHeight
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 486e305..97b86e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
-import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlagsClassic
@@ -57,18 +56,6 @@
         interactor.setShadeScrimBounds(bounds)
     }
 
-    /** Notifies that the bounds of the notification placeholder have changed. */
-    fun onStackBoundsChanged(
-        top: Float,
-        bottom: Float,
-    ) {
-        keyguardInteractor.setNotificationContainerBounds(
-            NotificationContainerBounds(top = top, bottom = bottom)
-        )
-        interactor.setStackTop(top)
-        interactor.setStackBottom(bottom)
-    }
-
     /** Sets the available space */
     fun onConstrainedAvailableSpaceChanged(height: Int) {
         interactor.setConstrainedAvailableSpace(height)
@@ -87,13 +74,6 @@
     val shadeScrimRounding: Flow<ShadeScrimRounding> =
         interactor.shadeScrimRounding.dumpWhileCollecting("shadeScrimRounding")
 
-    /**
-     * The height in px of the contents of notification stack. Depending on the number of
-     * notifications, this can exceed the space available on screen to show notifications, at which
-     * point the notification stack should become scrollable.
-     */
-    val stackHeight: StateFlow<Float> = interactor.stackHeight.dumpValue("stackHeight")
-
     /** The height in px of the contents of the HUN. */
     val headsUpHeight: StateFlow<Float> = interactor.headsUpHeight.dumpValue("headsUpHeight")
 
@@ -111,6 +91,13 @@
     val syntheticScroll: Flow<Float> =
         interactor.syntheticScroll.dumpWhileCollecting("syntheticScroll")
 
+    /**
+     * Whether the current touch gesture is overscroll. If true, it means the NSSL has already
+     * consumed part of the gesture.
+     */
+    val isCurrentGestureOverscroll: Flow<Boolean> =
+        interactor.isCurrentGestureOverscroll.dumpWhileCollecting("isCurrentGestureOverScroll")
+
     /** Sets whether the notification stack is scrolled to the top. */
     fun setScrolledToTop(scrolledToTop: Boolean) {
         interactor.setScrolledToTop(scrolledToTop)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index ac4bd09..3393321 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
@@ -64,12 +65,13 @@
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
 import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
-import com.android.systemui.util.kotlin.BooleanFlowOperators.and
-import com.android.systemui.util.kotlin.BooleanFlowOperators.or
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
+import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
 import com.android.systemui.util.kotlin.FlowDumperImpl
 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
 import javax.inject.Inject
@@ -246,7 +248,7 @@
                 keyguardTransitionInteractor.finishedKeyguardState.map { state ->
                     state == GLANCEABLE_HUB
                 },
-                or(
+                anyOf(
                     keyguardTransitionInteractor.isInTransitionToState(GLANCEABLE_HUB),
                     keyguardTransitionInteractor.isInTransitionFromState(GLANCEABLE_HUB),
                 ),
@@ -295,8 +297,7 @@
         return combine(
                 isOnLockscreenWithoutShade,
                 keyguardTransitionInteractor.isInTransition(
-                    from = LOCKSCREEN,
-                    to = AOD,
+                    edge = Edge.create(from = LOCKSCREEN, to = AOD)
                 ),
                 ::Pair
             )
@@ -424,14 +425,14 @@
                 while (currentCoroutineContext().isActive) {
                     emit(false)
                     // Ensure states are inactive to start
-                    and(
+                    allOf(
                             *toFlowArray(statesForHiddenKeyguard) { state ->
                                 keyguardTransitionInteractor.transitionValue(state).map { it == 0f }
                             }
                         )
                         .first { it }
                     // Wait for a qualifying transition to begin
-                    or(
+                    anyOf(
                             *toFlowArray(statesForHiddenKeyguard) { state ->
                                 keyguardTransitionInteractor
                                     .transitionStepsToState(state)
@@ -446,7 +447,7 @@
                     // it is considered safe to reset alpha to 1f for HUNs.
                     combine(
                             keyguardInteractor.statusBarState,
-                            and(
+                            allOf(
                                 *toFlowArray(statesForHiddenKeyguard) { state ->
                                     keyguardTransitionInteractor.transitionValue(state).map {
                                         it == 0f
@@ -636,7 +637,7 @@
                 showUnlimitedNotifications,
                 shadeInteractor.isUserInteracting,
                 availableHeight,
-                interactor.notificationStackChanged.onStart { emit(Unit) },
+                interactor.notificationStackChanged,
                 interactor.useExtraShelfSpace,
             ) { flows ->
                 val showLimitedNotifications = flows[0] as Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 7d97428..8fb552f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -283,12 +283,11 @@
     void awakenDreams();
 
     /**
-     * Handle a touch event while dreaming or on the glanceable hub when the touch was initiated
-     * within a prescribed swipeable area. This method is provided for cases where swiping in
-     * certain areas should be handled by CentralSurfaces instead (e.g. swiping hub open, opening
-     * the notification shade over dream or hub).
+     * Handle a touch event while dreaming when the touch was initiated within a prescribed
+     * swipeable area. This method is provided for cases where swiping in certain areas of a dream
+     * should be handled by CentralSurfaces instead (e.g. swiping communal hub open).
      */
-    void handleExternalShadeWindowTouch(MotionEvent event);
+    void handleDreamTouch(MotionEvent event);
 
     boolean isBouncerShowing();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index e93c0f6..7dac77e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -59,6 +59,7 @@
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeHeaderController;
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -82,6 +83,7 @@
     private final com.android.systemui.shade.ShadeController mShadeController;
     private final CommandQueue mCommandQueue;
     private final PanelExpansionInteractor mPanelExpansionInteractor;
+    private final Lazy<ShadeInteractor> mShadeInteractorLazy;
     private final ShadeHeaderController mShadeHeaderController;
     private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
     private final MetricsLogger mMetricsLogger;
@@ -121,6 +123,7 @@
             ShadeController shadeController,
             CommandQueue commandQueue,
             PanelExpansionInteractor panelExpansionInteractor,
+            Lazy<ShadeInteractor> shadeInteractorLazy,
             ShadeHeaderController shadeHeaderController,
             RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
             MetricsLogger metricsLogger,
@@ -148,6 +151,7 @@
         mShadeController = shadeController;
         mCommandQueue = commandQueue;
         mPanelExpansionInteractor = panelExpansionInteractor;
+        mShadeInteractorLazy = shadeInteractorLazy;
         mShadeHeaderController = shadeHeaderController;
         mRemoteInputQuickSettingsDisabler = remoteInputQuickSettingsDisabler;
         mMetricsLogger = metricsLogger;
@@ -487,14 +491,23 @@
     }
 
     @Override
-    public void togglePanel() {
-        if (mPanelExpansionInteractor.isPanelExpanded()) {
+    public void toggleNotificationsPanel() {
+        if (mShadeInteractorLazy.get().isAnyExpanded().getValue()) {
             mShadeController.animateCollapseShade();
         } else {
             mShadeController.animateExpandShade();
         }
     }
 
+    @Override
+    public void toggleQuickSettingsPanel() {
+        if (mShadeInteractorLazy.get().isQsExpanded().getValue()) {
+            mShadeController.animateCollapseShade();
+        } else {
+            mShadeController.animateExpandQs();
+        }
+    }
+
     private boolean isGoingToSleep() {
         return mWakefulnessLifecycle.getWakefulness()
                 == WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
index d5e66ff..8af7ee8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -79,7 +79,7 @@
     override fun updateScrimController() {}
     override fun shouldIgnoreTouch() = false
     override fun isDeviceInteractive() = false
-    override fun handleExternalShadeWindowTouch(event: MotionEvent?) {}
+    override fun handleDreamTouch(event: MotionEvent?) {}
     override fun awakenDreams() {}
     override fun isBouncerShowing() = false
     override fun isBouncerShowingScrimmed() = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index be6bef7..d3d2b1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static android.app.StatusBarManager.DISABLE_HOME;
 import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 import static android.app.StatusBarManager.WindowVisibleState;
@@ -1006,14 +1007,8 @@
                 // this handling this post-init task. We force an update in this case, and use a new
                 // token to not conflict with any other disabled flags already requested by SysUI
                 Binder token = new Binder();
-                int userId = mContext.getUserId();
-                StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
-                info.setNavigationHomeDisabled(true);
-                mBarService.disableForUser(info, token, mContext.getPackageName(),
-                        userId, "set the initial view visibility");
-
-                mBarService.disableForUser(new StatusBarManager.DisableInfo(), token,
-                        mContext.getPackageName(), userId, "set the initial view visibility");
+                mBarService.disable(DISABLE_HOME, token, mContext.getPackageName());
+                mBarService.disable(0, token, mContext.getPackageName());
             } catch (RemoteException ex) {
                 ex.rethrowFromSystemServer();
             }
@@ -2184,7 +2179,9 @@
         }
         if (mStatusBarStateController.leaveOpenOnKeyguardHide()) {
             if (!mStatusBarStateController.isKeyguardRequested()) {
-                mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
+                if (!MigrateClocksToBlueprint.isEnabled()) {
+                    mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
+                }
             }
             long delay = mKeyguardStateController.calculateGoingToFullShadeDelay();
             mLockscreenShadeTransitionController.onHideKeyguard(delay, previousState);
@@ -2808,7 +2805,16 @@
         mScrimController.setExpansionAffectsAlpha(!unlocking);
 
         if (mAlternateBouncerInteractor.isVisibleState()) {
-            if (!DeviceEntryUdfpsRefactor.isEnabled()) {
+            if (DeviceEntryUdfpsRefactor.isEnabled()) {
+                if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded())
+                        && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED
+                        || mTransitionToFullShadeProgress > 0f)) {
+                    // Assume scrim state for shade is already correct and do nothing
+                } else {
+                    // Safeguard which prevents the scrim from being stuck in the wrong state
+                    mScrimController.transitionTo(ScrimState.KEYGUARD);
+                }
+            } else {
                 if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded())
                         && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED
                         || mTransitionToFullShadeProgress > 0f)) {
@@ -2817,7 +2823,6 @@
                     mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED);
                 }
             }
-
             // This will cancel the keyguardFadingAway animation if it is running. We need to do
             // this as otherwise it can remain pending and leave keyguard in a weird state.
             mUnlockScrimCallback.onCancelled();
@@ -2935,8 +2940,8 @@
     };
 
     @Override
-    public void handleExternalShadeWindowTouch(MotionEvent event) {
-        getNotificationShadeWindowViewController().handleExternalTouch(event);
+    public void handleDreamTouch(MotionEvent event) {
+        getNotificationShadeWindowViewController().handleDreamTouch(event);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index dea9416..2e1ab38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -59,7 +59,10 @@
     }
 
     override fun notifyThemeChanged() {
-        val listeners = ArrayList(listeners)
+        // Avoid concurrent modification exception
+        val listeners = synchronized(this.listeners) {
+           ArrayList(this.listeners)
+        }
 
         listeners.filterForEach({ this.listeners.contains(it) }) {
             it.onThemeChanged()
@@ -68,8 +71,9 @@
 
     override fun onConfigurationChanged(newConfig: Configuration) {
         // Avoid concurrent modification exception
-        val listeners = ArrayList(listeners)
-
+        val listeners = synchronized(this.listeners) {
+           ArrayList(this.listeners)
+        }
         listeners.filterForEach({ this.listeners.contains(it) }) {
             it.onConfigChanged(newConfig)
         }
@@ -148,12 +152,16 @@
     }
 
     override fun addCallback(listener: ConfigurationListener) {
-        listeners.add(listener)
+        synchronized(listeners) {
+            listeners.add(listener)
+        }
         listener.onDensityOrFontScaleChanged()
     }
 
     override fun removeCallback(listener: ConfigurationListener) {
-        listeners.remove(listener)
+        synchronized(listeners) {
+            listeners.remove(listener)
+        }
     }
 
     override fun isLayoutRtl(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index f219b9d..2b26e3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -54,7 +54,6 @@
 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.shade.ShadeViewStateProvider;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.disableflags.DisableStateTracker;
@@ -133,9 +132,6 @@
     private View mSystemIconsContainer;
     private final StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory;
 
-    // TODO(b/273443374): remove
-    private NotificationMediaManager mNotificationMediaManager;
-
     private final ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
                 @Override
@@ -302,7 +298,6 @@
             @Main Executor mainExecutor,
             @Background Executor backgroundExecutor,
             KeyguardLogger logger,
-            NotificationMediaManager notificationMediaManager,
             StatusOverlayHoverListenerFactory statusOverlayHoverListenerFactory
     ) {
         super(view);
@@ -357,7 +352,6 @@
                 /* mask2= */ DISABLE2_SYSTEM_ICONS,
                 this::updateViewState
         );
-        mNotificationMediaManager = notificationMediaManager;
         mStatusOverlayHoverListenerFactory = statusOverlayHoverListenerFactory;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 0a88d63..fe001b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -64,6 +64,7 @@
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.Edge;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.keyguard.shared.model.ScrimAlpha;
 import com.android.systemui.keyguard.shared.model.TransitionState;
@@ -71,6 +72,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.model.Scenes;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
@@ -454,23 +456,32 @@
                 };
 
         // PRIMARY_BOUNCER->GONE
-        collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(PRIMARY_BOUNCER, GONE),
+        collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(
+                Edge.Companion.create(PRIMARY_BOUNCER, GONE)),
                 mBouncerToGoneTransition, mMainDispatcher);
         collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha(),
                 mScrimAlphaConsumer, mMainDispatcher);
 
         // ALTERNATE_BOUNCER->GONE
-        collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(ALTERNATE_BOUNCER, GONE),
+        collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(
+                Edge.Companion.create(ALTERNATE_BOUNCER, Scenes.Gone),
+                Edge.Companion.create(ALTERNATE_BOUNCER, GONE)),
                 mBouncerToGoneTransition, mMainDispatcher);
         collectFlow(behindScrim, mAlternateBouncerToGoneTransitionViewModel.getScrimAlpha(),
                 mScrimAlphaConsumer, mMainDispatcher);
 
         // LOCKSCREEN<->GLANCEABLE_HUB
+        collectFlow(
+                behindScrim,
+                mKeyguardTransitionInteractor.transition(
+                        Edge.Companion.create(LOCKSCREEN, Scenes.Communal),
+                        Edge.Companion.create(LOCKSCREEN, GLANCEABLE_HUB)),
+                mGlanceableHubConsumer,
+                mMainDispatcher);
         collectFlow(behindScrim,
-                mKeyguardTransitionInteractor.transition(LOCKSCREEN, GLANCEABLE_HUB),
-                mGlanceableHubConsumer, mMainDispatcher);
-        collectFlow(behindScrim,
-                mKeyguardTransitionInteractor.transition(GLANCEABLE_HUB, LOCKSCREEN),
+                mKeyguardTransitionInteractor.transition(
+                        Edge.Companion.create(Scenes.Communal, LOCKSCREEN),
+                        Edge.Companion.create(GLANCEABLE_HUB, LOCKSCREEN)),
                 mGlanceableHubConsumer, mMainDispatcher);
     }
 
@@ -1644,6 +1655,10 @@
         mScreenOn = false;
     }
 
+    public boolean isScreenOn() {
+        return mScreenOn;
+    }
+
     public void setExpansionAffectsAlpha(boolean expansionAffectsAlpha) {
         mExpansionAffectsAlpha = expansionAffectsAlpha;
     }
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 f0dab3b..fa88be5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -696,6 +696,7 @@
         Trace.beginSection("StatusBarKeyguardViewManager#show");
         mNotificationShadeWindowController.setKeyguardShowing(true);
         if (SceneContainerFlag.isEnabled()) {
+            // TODO(b/336581871): add sceneState?
             mSceneInteractorLazy.get().changeScene(
                     Scenes.Lockscreen, "StatusBarKeyguardViewManager.show");
         }
@@ -1548,7 +1549,8 @@
         }
 
         if (KeyguardWmStateRefactor.isEnabled()) {
-            mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+            mKeyguardTransitionInteractor.startDismissKeyguardTransition(
+                    "SBKVM#keyguardAuthenticated");
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index e1a7f22..e92058b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -96,6 +96,20 @@
 @SysUISingleton
 public class StatusBarNotificationActivityStarter implements NotificationActivityStarter {
 
+    /**
+     * Helps to avoid recalculation of values provided to
+     * {@link #onDismiss(PendingIntent, boolean, boolean, boolean)}} method
+     */
+    private interface OnKeyguardDismissedAction {
+        /**
+         * Invoked when keyguard is dismissed
+         *
+         * @return is used as return value for {@link ActivityStarter.OnDismissAction#onDismiss()}
+         */
+        boolean onDismiss(PendingIntent intent, boolean isActivityIntent, boolean animate,
+                boolean showOverTheLockScreen);
+    }
+
     private final Context mContext;
     private final int mDisplayId;
 
@@ -207,6 +221,30 @@
     }
 
     /**
+     * Called when the user clicks on the notification bubble icon.
+     *
+     * @param entry notification that bubble icon was clicked
+     */
+    @Override
+    public void onNotificationBubbleIconClicked(NotificationEntry entry) {
+        Runnable action = () -> {
+            mBubblesManagerOptional.ifPresent(bubblesManager ->
+                    bubblesManager.onUserChangedBubble(entry, !entry.isBubble()));
+            mHeadsUpManager.removeNotification(entry.getKey(), /* releaseImmediately= */ true);
+        };
+        if (entry.isBubble()) {
+            // entry is being un-bubbled, no need to unlock
+            action.run();
+        } else {
+            performActionAfterKeyguardDismissed(entry,
+                    (intent, isActivityIntent, animate, showOverTheLockScreen) -> {
+                        action.run();
+                        return false;
+                    });
+        }
+    }
+
+    /**
      * Called when a notification is clicked.
      *
      * @param entry notification that was clicked
@@ -217,7 +255,15 @@
         mLogger.logStartingActivityFromClick(entry, row.isHeadsUpState(),
                 mKeyguardStateController.isVisible(),
                 mNotificationShadeWindowController.getPanelExpanded());
+        OnKeyguardDismissedAction action =
+                (intent, isActivityIntent, animate, showOverTheLockScreen) ->
+                        performActionOnKeyguardDismissed(entry, row, intent, isActivityIntent,
+                                animate, showOverTheLockScreen);
+        performActionAfterKeyguardDismissed(entry, action);
+    }
 
+    private void performActionAfterKeyguardDismissed(NotificationEntry entry,
+            OnKeyguardDismissedAction action) {
         if (mRemoteInputManager.isRemoteInputActive(entry)) {
             // We have an active remote input typed and the user clicked on the notification.
             // this was probably unintentional, so we're closing the edit text instead.
@@ -251,8 +297,7 @@
         ActivityStarter.OnDismissAction postKeyguardAction = new ActivityStarter.OnDismissAction() {
             @Override
             public boolean onDismiss() {
-                return handleNotificationClickAfterKeyguardDismissed(
-                        entry, row, intent, isActivityIntent, animate, showOverLockscreen);
+                return action.onDismiss(intent, isActivityIntent, animate, showOverLockscreen);
             }
 
             @Override
@@ -271,7 +316,7 @@
         }
     }
 
-    private boolean handleNotificationClickAfterKeyguardDismissed(
+    private boolean performActionOnKeyguardDismissed(
             NotificationEntry entry,
             ExpandableNotificationRow row,
             PendingIntent intent,
@@ -282,7 +327,6 @@
 
         final Runnable runnable = () -> handleNotificationClickAfterPanelCollapsed(
                 entry, row, intent, isActivityIntent, animate);
-
         if (showOverLockscreen) {
             mShadeController.addPostCollapseAction(runnable);
             mShadeController.collapseShade(true /* animate */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index a6284e3..4505a1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -196,7 +196,22 @@
                 // The group isn't expanded, let's make sure it's visible!
                 mGroupExpansionManager.toggleGroupExpansion(row.getEntry());
             }
-            row.setUserExpanded(true);
+
+            if (android.app.Flags.compactHeadsUpNotificationReply()
+                    && row.isCompactConversationHeadsUpOnScreen()) {
+                // Notification can be system expanded true and it is set user expanded in
+                // activateRemoteInput. notifyHeightChanged also doesn't work as visibleType doesn't
+                // change. To expand huning notification properly, we need set userExpanded false.
+                if (!row.isPinned() && row.isExpanded()) {
+                    row.setUserExpanded(false);
+                }
+                // expand notification emits expanded information to HUN listener.
+                row.expandNotification();
+            } else {
+                // Note: Since Normal HUN has remote input view in it, we don't expect to hit
+                // onMakeExpandedVisibleForRemoteInput from activateRemoteInput for Normal HUN.
+                row.setUserExpanded(true);
+            }
             row.getPrivateLayout().setOnExpandedVisibleListener(runnable);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 4fc11df..a858fb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -119,7 +119,7 @@
     private MultiSourceMinAlphaController mEndSideAlphaController;
     private LinearLayout mEndSideContent;
     private View mClockView;
-    private View mOngoingCallChip;
+    private View mOngoingActivityChip;
     private View mNotificationIconAreaInner;
     // Visibilities come in from external system callers via disable flags, but we also sometimes
     // modify the visibilities internally. We need to store both so that we don't accidentally
@@ -345,7 +345,7 @@
         mEndSideContent = mStatusBar.findViewById(R.id.status_bar_end_side_content);
         mEndSideAlphaController = new MultiSourceMinAlphaController(mEndSideContent);
         mClockView = mStatusBar.findViewById(R.id.clock);
-        mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip);
+        mOngoingActivityChip = mStatusBar.findViewById(R.id.ongoing_activity_chip);
         showEndSideContent(false);
         showClock(false);
         initOperatorName();
@@ -594,9 +594,9 @@
         // so if the icons are disabled then the call chip should be, too.)
         boolean showOngoingCallChip = hasOngoingCall && !disableNotifications;
         if (showOngoingCallChip) {
-            showOngoingCallChip(animate);
+            showOngoingActivityChip(animate);
         } else {
-            hideOngoingCallChip(animate);
+            hideOngoingActivityChip(animate);
         }
         mOngoingCallController.notifyChipVisibilityChanged(showOngoingCallChip);
     }
@@ -688,14 +688,19 @@
         animateShow(mClockView, animate);
     }
 
-    /** Hides the ongoing call chip. */
-    public void hideOngoingCallChip(boolean animate) {
-        animateHiddenState(mOngoingCallChip, View.GONE, animate);
+    /** Hides the ongoing activity chip. */
+    private void hideOngoingActivityChip(boolean animate) {
+        animateHiddenState(mOngoingActivityChip, View.GONE, animate);
     }
 
-    /** Displays the ongoing call chip. */
-    public void showOngoingCallChip(boolean animate) {
-        animateShow(mOngoingCallChip, animate);
+    /**
+     * Displays the ongoing activity chip.
+     *
+     * For now, this chip will only ever contain the ongoing call information, but after b/332662551
+     * feature is implemented, it will support different kinds of ongoing activities.
+     */
+    private void showOngoingActivityChip(boolean animate) {
+        animateShow(mOngoingActivityChip, animate);
     }
 
     /**
@@ -803,7 +808,7 @@
 
     private void initOngoingCallChip() {
         mOngoingCallController.addCallback(mOngoingCallListener);
-        mOngoingCallController.setChipView(mOngoingCallChip);
+        mOngoingCallController.setChipView(mOngoingActivityChip);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index ec88b6c..a7d4ce3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -36,6 +36,8 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
+import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
 import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -145,8 +147,8 @@
     fun setChipView(chipView: View) {
         tearDownChipView()
         this.chipView = chipView
-        val backgroundView: OngoingCallBackgroundContainer? =
-            chipView.findViewById(R.id.ongoing_call_chip_background)
+        val backgroundView: ChipBackgroundContainer? =
+            chipView.findViewById(R.id.ongoing_activity_chip_background)
         backgroundView?.maxHeightFetcher = { statusBarWindowController.statusBarHeight }
         if (hasOngoingCall()) {
             updateChip()
@@ -226,7 +228,7 @@
         if (callNotificationInfo == null) { return }
         val currentChipView = chipView
         val backgroundView =
-            currentChipView?.findViewById<View>(R.id.ongoing_call_chip_background)
+            currentChipView?.findViewById<View>(R.id.ongoing_activity_chip_background)
         val intent = callNotificationInfo?.intent
         if (currentChipView != null && backgroundView != null && intent != null) {
             currentChipView.setOnClickListener {
@@ -271,8 +273,8 @@
     @VisibleForTesting
     fun tearDownChipView() = chipView?.getTimeView()?.stop()
 
-    private fun View.getTimeView(): OngoingCallChronometer? {
-        return this.findViewById(R.id.ongoing_call_chip_time)
+    private fun View.getTimeView(): ChipChronometer? {
+        return this.findViewById(R.id.ongoing_activity_chip_time)
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
index 9c78ab4..886481e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 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.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 226a84a..88ca9e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -229,7 +229,7 @@
         @SysUISingleton
         @OemSatelliteInputLog
         fun provideOemSatelliteInputLog(factory: LogBufferFactory): LogBuffer {
-            return factory.create("DeviceBasedSatelliteInputLog", 32)
+            return factory.create("DeviceBasedSatelliteInputLog", 150)
         }
 
         const val FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index d4b2dbf..2e54972 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -53,6 +53,27 @@
         )
     }
 
+    fun logTopLevelServiceStateBroadcastEmergencyOnly(subId: Int, serviceState: ServiceState) {
+        buffer.log(
+            TAG,
+            LogLevel.INFO,
+            {
+                int1 = subId
+                bool1 = serviceState.isEmergencyOnly
+            },
+            { "ACTION_SERVICE_STATE for subId=$int1. ServiceState.isEmergencyOnly=$bool1" }
+        )
+    }
+
+    fun logTopLevelServiceStateBroadcastMissingExtras(subId: Int) {
+        buffer.log(
+            TAG,
+            LogLevel.INFO,
+            { int1 = subId },
+            { "ACTION_SERVICE_STATE for subId=$int1. Intent is missing extras. Ignoring" }
+        )
+    }
+
     fun logOnSignalStrengthsChanged(signalStrength: SignalStrength, subId: Int) {
         buffer.log(
             TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ServiceStateModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ServiceStateModel.kt
new file mode 100644
index 0000000..cce3eb0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ServiceStateModel.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.statusbar.pipeline.mobile.data.model
+
+import android.telephony.ServiceState
+
+/**
+ * Simplified representation of a [ServiceState] for use in SystemUI. Add any fields that we need to
+ * extract from service state here for consumption downstream
+ */
+data class ServiceStateModel(val isEmergencyOnly: Boolean) {
+    companion object {
+        fun fromServiceState(serviceState: ServiceState): ServiceStateModel {
+            return ServiceStateModel(isEmergencyOnly = serviceState.isEmergencyOnly)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
index d9d909a..fc54f14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
@@ -33,6 +33,18 @@
      */
     val isOpportunistic: Boolean = false,
 
+    /**
+     * True if this subscription **only** supports non-terrestrial networks (NTN) and false
+     * otherwise. (non-terrestrial == satellite)
+     *
+     * Note that we intend to filter these subscriptions out, because these connections are actually
+     * supported by
+     * [com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository]. See
+     * [com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor] for
+     * the filtering.
+     */
+    val isExclusivelyNonTerrestrial: Boolean = false,
+
     /** Subscriptions in the same group may be filtered or treated as a single subscription */
     val groupUuid: ParcelUuid? = null,
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 2278597..425c58b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import kotlinx.coroutines.flow.StateFlow
 
@@ -76,7 +77,17 @@
      */
     val isInService: StateFlow<Boolean>
 
-    /** Reflects [android.telephony.ServiceState.isUsingNonTerrestrialNetwork] */
+    /**
+     * True if this subscription is actively connected to a non-terrestrial network and false
+     * otherwise. Reflects [android.telephony.ServiceState.isUsingNonTerrestrialNetwork].
+     *
+     * Notably: This value reflects that this subscription is **currently** using a non-terrestrial
+     * network, because some subscriptions can switch between terrestrial and non-terrestrial
+     * networks. [SubscriptionModel.isExclusivelyNonTerrestrial] reflects whether a subscription is
+     * configured to exclusively connect to non-terrestrial networks. [isNonTerrestrial] can change
+     * during the lifetime of a subscription but [SubscriptionModel.isExclusivelyNonTerrestrial]
+     * will stay constant.
+     */
     val isNonTerrestrial: StateFlow<Boolean>
 
     /** True if [android.telephony.SignalStrength] told us that this connection is using GSM */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index 9471574..5ad8bf1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -21,6 +21,7 @@
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.settingslib.mobile.MobileMappings
 import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
@@ -92,6 +93,19 @@
     val defaultMobileIconGroup: Flow<MobileIconGroup>
 
     /**
+     * [deviceServiceState] is equivalent to the last [Intent.ACTION_SERVICE_STATE] broadcast with a
+     * subscriptionId of -1 (aka [SubscriptionManager.INVALID_SUBSCRIPTION_ID]).
+     *
+     * While each [MobileConnectionsRepository] listens for the service state of each subscription,
+     * there is potentially a service state associated with the device itself. This value can be
+     * used to calculate e.g., the emergency calling capability of the device (as opposed to the
+     * emergency calling capability of an individual mobile connection)
+     *
+     * Note: this is a [StateFlow] using an eager sharing strategy.
+     */
+    val deviceServiceState: StateFlow<ServiceStateModel?>
+
+    /**
      * If any active SIM on the device is in
      * [android.telephony.TelephonyManager.SIM_STATE_PIN_REQUIRED] or
      * [android.telephony.TelephonyManager.SIM_STATE_PUK_REQUIRED] or
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index 8a8e33e..b068152 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.demomode.DemoMode
 import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl
@@ -151,6 +152,15 @@
     override val defaultMobileIconGroup: Flow<SignalIcon.MobileIconGroup> =
         activeRepo.flatMapLatest { it.defaultMobileIconGroup }
 
+    override val deviceServiceState: StateFlow<ServiceStateModel?> =
+        activeRepo
+            .flatMapLatest { it.deviceServiceState }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                realRepository.deviceServiceState.value
+            )
+
     override val isAnySimSecure: Flow<Boolean> = activeRepo.flatMapLatest { it.isAnySimSecure }
     override fun getIsAnySimSecure(): Boolean = activeRepo.value.getIsAnySimSecure()
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index 2b3c632..a944e91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
@@ -136,6 +137,9 @@
 
     override val defaultMobileIconGroup = flowOf(TelephonyIcons.THREE_G)
 
+    // TODO(b/339023069): demo command for device-based connectivity state
+    override val deviceServiceState: StateFlow<ServiceStateModel?> = MutableStateFlow(null)
+
     override val isAnySimSecure: Flow<Boolean> = flowOf(getIsAnySimSecure())
     override fun getIsAnySimSecure(): Boolean = false
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 5d91ef3..c32f0e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -18,8 +18,10 @@
 
 import android.annotation.SuppressLint
 import android.content.Context
+import android.content.Intent
 import android.content.IntentFilter
 import android.telephony.CarrierConfigManager
+import android.telephony.ServiceState
 import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
@@ -35,7 +37,6 @@
 import com.android.settingslib.mobile.MobileMappings.Config
 import com.android.systemui.Dumpable
 import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -47,6 +48,7 @@
 import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
@@ -55,6 +57,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import java.io.PrintWriter
 import java.lang.ref.WeakReference
 import javax.inject.Inject
@@ -68,6 +71,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
@@ -169,6 +173,35 @@
             }
             .flowOn(bgDispatcher)
 
+    /** Note that this flow is eager, so we don't miss any state */
+    override val deviceServiceState: StateFlow<ServiceStateModel?> =
+        broadcastDispatcher
+            .broadcastFlow(IntentFilter(Intent.ACTION_SERVICE_STATE)) { intent, _ ->
+                val subId =
+                    intent.getIntExtra(
+                        SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
+                        INVALID_SUBSCRIPTION_ID
+                    )
+
+                val extras = intent.extras
+                if (extras == null) {
+                    logger.logTopLevelServiceStateBroadcastMissingExtras(subId)
+                    return@broadcastFlow null
+                }
+
+                val serviceState = ServiceState.newFromBundle(extras)
+                logger.logTopLevelServiceStateBroadcastEmergencyOnly(subId, serviceState)
+                if (subId == INVALID_SUBSCRIPTION_ID) {
+                    // Assume that -1 here is the device's service state. We don't care about
+                    // other ones.
+                    ServiceStateModel.fromServiceState(serviceState)
+                } else {
+                    null
+                }
+            }
+            .filterNotNull()
+            .stateIn(scope, SharingStarted.Eagerly, null)
+
     /**
      * State flow that emits the set of mobile data subscriptions, each represented by its own
      * [SubscriptionModel].
@@ -424,6 +457,7 @@
         SubscriptionModel(
             subscriptionId = subscriptionId,
             isOpportunistic = isOpportunistic,
+            isExclusivelyNonTerrestrial = isOnlyNonTerrestrialNetwork,
             groupUuid = groupUuid,
             carrierName = carrierName.toString(),
             profileClass = profileClass,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index d555c47..cc4d568 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -111,6 +111,13 @@
     val isForceHidden: Flow<Boolean>
 
     /**
+     * True if the device-level service state (with -1 subscription id) reports emergency calls
+     * only. This value is only useful when there are no other subscriptions OR all existing
+     * subscriptions report that they are not in service.
+     */
+    val isDeviceInEmergencyCallsOnlyMode: Flow<Boolean>
+
+    /**
      * Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given
      * subId.
      */
@@ -172,21 +179,33 @@
     private val unfilteredSubscriptions: Flow<List<SubscriptionModel>> =
         mobileConnectionsRepo.subscriptions
 
-    /**
-     * Any filtering that we can do based purely on the info of each subscription. Currently this
-     * only applies the ProfileClass-based filter, but if we need other they can go here
-     */
+    /** Any filtering that we can do based purely on the info of each subscription individually. */
     private val subscriptionsBasedFilteredSubs =
-        unfilteredSubscriptions.map { subs -> applyProvisioningFilter(subs) }.distinctUntilChanged()
+        unfilteredSubscriptions
+            .map { it.filterBasedOnProvisioning().filterBasedOnNtn() }
+            .distinctUntilChanged()
 
-    private fun applyProvisioningFilter(subs: List<SubscriptionModel>): List<SubscriptionModel> =
+    private fun List<SubscriptionModel>.filterBasedOnProvisioning(): List<SubscriptionModel> =
         if (!featureFlagsClassic.isEnabled(FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS)) {
-            subs
+            this
         } else {
-            subs.filter { it.profileClass != PROFILE_CLASS_PROVISIONING }
+            this.filter { it.profileClass != PROFILE_CLASS_PROVISIONING }
         }
 
     /**
+     * Subscriptions that exclusively support non-terrestrial networks should **never** directly
+     * show any iconography in the status bar. These subscriptions only exist to provide a backing
+     * for the device-based satellite connections, and the iconography for those connections are
+     * already being handled in
+     * [com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository]. We
+     * need to filter out those subscriptions here so we guarantee the subscription never turns into
+     * an icon. See b/336881301.
+     */
+    private fun List<SubscriptionModel>.filterBasedOnNtn(): List<SubscriptionModel> {
+        return this.filter { !it.isExclusivelyNonTerrestrial }
+    }
+
+    /**
      * Generally, SystemUI wants to show iconography for each subscription that is listed by
      * [SubscriptionManager]. However, in the case of opportunistic subscriptions, we want to only
      * show a single representation of the pair of subscriptions. The docs define opportunistic as:
@@ -204,12 +223,8 @@
                 subscriptionsBasedFilteredSubs,
                 mobileConnectionsRepo.activeMobileDataSubscriptionId,
                 connectivityRepository.vcnSubId,
-            ) { unfilteredSubs, activeId, vcnSubId ->
-                filterSubsBasedOnOpportunistic(
-                    unfilteredSubs,
-                    activeId,
-                    vcnSubId,
-                )
+            ) { preFilteredSubs, activeId, vcnSubId ->
+                filterSubsBasedOnOpportunistic(preFilteredSubs, activeId, vcnSubId)
             }
             .distinctUntilChanged()
             .logDiffsForTable(
@@ -369,6 +384,9 @@
             .map { it.contains(ConnectivitySlot.MOBILE) }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
+    override val isDeviceInEmergencyCallsOnlyMode: Flow<Boolean> =
+        mobileConnectionsRepo.deviceServiceState.map { it?.isEmergencyOnly ?: false }
+
     /** Vends out new [MobileIconInteractor] for a particular subId */
     override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
         reuseCache[subId]?.get() ?: createMobileConnectionInteractorForSubId(subId)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index 0739b836..12f252d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -249,11 +249,17 @@
                 try {
                     sm.registerForNtnSignalStrengthChanged(bgDispatcher.asExecutor(), cb)
                     registered = true
+                    logBuffer.i { "Registered for signal strength successfully" }
                 } catch (e: Exception) {
                     logBuffer.e("error registering for signal strength", e)
                 }
 
-                awaitClose { if (registered) sm.unregisterForNtnSignalStrengthChanged(cb) }
+                awaitClose {
+                    if (registered) {
+                        sm.unregisterForNtnSignalStrengthChanged(cb)
+                        logBuffer.i { "Unregistered for signal strength successfully" }
+                    }
+                }
             }
             .flowOn(bgDispatcher)
 
@@ -318,8 +324,8 @@
         }
 
     companion object {
-        // TTL for satellite polling is one hour
-        const val POLLING_INTERVAL_MS: Long = 1000 * 60 * 60
+        // TTL for satellite polling is twenty minutes
+        const val POLLING_INTERVAL_MS: Long = 1000 * 60 * 20
 
         // Let the system boot up and stabilize before we check for system support
         const val MIN_UPTIME: Long = 1000 * 60
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
index 51c053e..5b954b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
@@ -19,6 +19,9 @@
 import com.android.internal.telephony.flags.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.statusbar.pipeline.dagger.OemSatelliteInputLog
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
 import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
@@ -45,6 +48,7 @@
     deviceProvisioningInteractor: DeviceProvisioningInteractor,
     wifiInteractor: WifiInteractor,
     @Application scope: CoroutineScope,
+    @OemSatelliteInputLog private val logBuffer: LogBuffer,
 ) {
     /** Must be observed by any UI showing Satellite iconography */
     val isSatelliteAllowed =
@@ -79,25 +83,52 @@
     val isWifiActive: Flow<Boolean> =
         wifiInteractor.wifiNetwork.map { it is WifiNetworkModel.Active }
 
+    private val allConnectionsOos =
+        iconsInteractor.icons.aggregateOver(
+            selector = { intr ->
+                combine(intr.isInService, intr.isEmergencyOnly, intr.isNonTerrestrial) {
+                    isInService,
+                    isEmergencyOnly,
+                    isNtn ->
+                    !isInService && !isEmergencyOnly && !isNtn
+                }
+            },
+            defaultValue = true, // no connections == everything is OOS
+        ) { isOosAndNotEmergencyAndNotSatellite ->
+            isOosAndNotEmergencyAndNotSatellite.all { it }
+        }
+
     /** When all connections are considered OOS, satellite connectivity is potentially valid */
     val areAllConnectionsOutOfService =
         if (Flags.oemEnabledSatelliteFlag()) {
-                iconsInteractor.icons.aggregateOver(
-                    selector = { intr ->
-                        combine(intr.isInService, intr.isEmergencyOnly, intr.isNonTerrestrial) {
-                            isInService,
-                            isEmergencyOnly,
-                            isNtn ->
-                            !isInService && !(isEmergencyOnly || isNtn)
-                        }
-                    }
-                ) { isOosAndNotEmergencyOnlyOrSatellite ->
-                    isOosAndNotEmergencyOnlyOrSatellite.all { it }
+                combine(
+                    allConnectionsOos,
+                    iconsInteractor.isDeviceInEmergencyCallsOnlyMode,
+                ) { connectionsOos, deviceEmergencyOnly ->
+                    logBuffer.log(
+                        TAG,
+                        LogLevel.INFO,
+                        {
+                            bool1 = connectionsOos
+                            bool2 = deviceEmergencyOnly
+                        },
+                        {
+                            "Updating OOS status. allConnectionsOOs=$bool1 " +
+                                "deviceEmergencyOnly=$bool2"
+                        },
+                    )
+                    // If no connections exist, or all are OOS, then we look to the device-based
+                    // service state to detect if any calls are possible
+                    connectionsOos && !deviceEmergencyOnly
                 }
             } else {
                 flowOf(false)
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), true)
+
+    companion object {
+        const val TAG = "DeviceBasedSatelliteInteractor"
+    }
 }
 
 /**
@@ -106,12 +137,22 @@
  *
  * Provides a way to connect the reactivity of the top-level flow with the reactivity of an
  * arbitrarily-defined relationship ([selector]) from R to the flow that R exposes.
+ *
+ * [defaultValue] allows for a default value to be used if there are no leaf nodes after applying
+ * [selector]. E.g., if there are no mobile connections, assume that there is no service.
  */
 @OptIn(ExperimentalCoroutinesApi::class)
 private inline fun <R, reified S, T> Flow<List<R>>.aggregateOver(
     crossinline selector: (R) -> Flow<S>,
-    crossinline transform: (Array<S>) -> T
+    defaultValue: T,
+    crossinline transform: (Array<S>) -> T,
 ): Flow<T> {
     return map { list -> list.map { selector(it) } }
-        .flatMapLatest { newFlows -> combine(newFlows) { newVals -> transform(newVals) } }
+        .flatMapLatest { newFlows ->
+            if (newFlows.isEmpty()) {
+                flowOf(defaultValue)
+            } else {
+                combine(newFlows) { newVals -> transform(newVals) }
+            }
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
index cc87e8a..0a6e95e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
@@ -81,12 +82,12 @@
 ) : CollapsedStatusBarViewModel {
     override val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean> =
         keyguardTransitionInteractor
-            .isInTransition(LOCKSCREEN, OCCLUDED)
+            .isInTransition(Edge.create(from = LOCKSCREEN, to = OCCLUDED))
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = false)
 
     override val transitionFromLockscreenToDreamStartedEvent: Flow<Unit> =
         keyguardTransitionInteractor
-            .transition(LOCKSCREEN, DREAMING)
+            .transition(Edge.create(from = LOCKSCREEN, to = DREAMING))
             .filter { it.transitionState == TransitionState.STARTED }
             .map {}
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index 2670a95..8b48bd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
 import com.android.systemui.statusbar.policy.BaseHeadsUpManager.HeadsUpEntry
+import com.android.systemui.util.Compile
 import java.io.PrintWriter
 import javax.inject.Inject
 
@@ -30,12 +31,14 @@
  * succession, by delaying visual listener side effects and removal handling from BaseHeadsUpManager
  */
 @SysUISingleton
-class AvalancheController @Inject constructor(
+class AvalancheController
+@Inject
+constructor(
     dumpManager: DumpManager,
 ) : Dumpable {
 
     private val tag = "AvalancheController"
-    private val debug = false
+    private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG)
 
     // HUN showing right now, in the floating state where full shade is hidden, on launcher or AOD
     @VisibleForTesting var headsUpEntryShowing: HeadsUpEntry? = null
@@ -79,7 +82,7 @@
         val fn = "[$label] => AvalancheController.update [${getKey(entry)}]"
         if (entry == null) {
             log { "Entry is NULL, stop update." }
-            return;
+            return
         }
         if (debug) {
             debugRunnableLabelMap[runnable] = label
@@ -106,7 +109,10 @@
             if (isOnlyNextEntry) {
                 // HeadsUpEntry.updateEntry recursively calls AvalancheController#update
                 // and goes to the isShowing case above
-                headsUpEntryShowing!!.updateEntry(false, "avalanche duration update")
+                headsUpEntryShowing!!.updateEntry(
+                        /* updatePostTime= */ false,
+                        /* updateEarliestRemovalTime= */ false,
+                        /* reason= */ "avalanche duration update")
             }
         }
         logState("after $fn")
@@ -142,9 +148,12 @@
         } else if (isShowing(entry)) {
             log { "$fn => [remove showing ${getKey(entry)}]" }
             previousHunKey = getKey(headsUpEntryShowing)
-
+            // Show the next HUN before removing this one, so that we don't tell listeners
+            // onHeadsUpPinnedModeChanged, which causes
+            // NotificationPanelViewController.updateTouchableRegion to hide the window while the
+            // HUN is animating out, resulting in a flicker.
+            showNext()
             runnable.run()
-            showNextAfterRemove()
         } else {
             log { "$fn => [removing untracked ${getKey(entry)}]" }
         }
@@ -247,12 +256,13 @@
         }
     }
 
-    private fun showNextAfterRemove() {
+    private fun showNext() {
         log { "SHOW NEXT" }
         headsUpEntryShowing = null
 
         if (nextList.isEmpty()) {
             log { "NO MORE TO SHOW" }
+            previousHunKey = ""
             return
         }
 
@@ -293,17 +303,21 @@
 
     private fun getStateStr(): String {
         return "SHOWING: [${getKey(headsUpEntryShowing)}]" +
-                "\nPREVIOUS: [$previousHunKey]" +
-                "\nNEXT LIST: $nextListStr" +
-                "\nNEXT MAP: $nextMapStr" +
-                "\nDROPPED: $dropSetStr"
+            "\nPREVIOUS: [$previousHunKey]" +
+            "\nNEXT LIST: $nextListStr" +
+            "\nNEXT MAP: $nextMapStr" +
+            "\nDROPPED: $dropSetStr"
     }
 
     private fun logState(reason: String) {
-        log { "\n================================================================================="}
+        log {
+            "\n================================================================================="
+        }
         log { "STATE $reason" }
         log { getStateStr() }
-        log { "=================================================================================\n"}
+        log {
+            "=================================================================================\n"
+        }
     }
 
     private val dropSetStr: String
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index b8318a7..a7fe49b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -39,6 +39,7 @@
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
 import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
 import com.android.systemui.util.ListenerSet;
 import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -114,7 +115,8 @@
         mUiEventLogger = uiEventLogger;
         mAvalancheController = avalancheController;
         Resources resources = context.getResources();
-        mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
+        mMinimumDisplayTime = NotificationThrottleHun.isEnabled()
+                ? 500 : resources.getInteger(R.integer.heads_up_notification_minimum_time);
         mStickyForSomeTimeAutoDismissTime = resources.getInteger(
                 R.integer.sticky_heads_up_notification_time);
         mAutoDismissTime = resources.getInteger(R.integer.heads_up_notification_decay);
@@ -765,11 +767,23 @@
          * @param updatePostTime whether or not to refresh the post time
          */
         public void updateEntry(boolean updatePostTime, @Nullable String reason) {
+            updateEntry(updatePostTime, /* updateEarliestRemovalTime= */ true, reason);
+        }
+
+        /**
+         * Updates an entry's removal time.
+         * @param updatePostTime whether or not to refresh the post time
+         * @param updateEarliestRemovalTime whether this update should further delay removal
+         */
+        public void updateEntry(boolean updatePostTime, boolean updateEarliestRemovalTime,
+                @Nullable String reason) {
             Runnable runnable = () -> {
                 mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
 
                 final long now = mSystemClock.elapsedRealtime();
-                mEarliestRemovalTime = now + mMinimumDisplayTime;
+                if (updateEarliestRemovalTime) {
+                    mEarliestRemovalTime = now + mMinimumDisplayTime;
+                }
 
                 if (updatePostTime) {
                     mPostTime = Math.max(mPostTime, now);
@@ -785,7 +799,9 @@
             FinishTimeUpdater finishTimeCalculator = () -> {
                 final long finishTime = calculateFinishTime();
                 final long now = mSystemClock.elapsedRealtime();
-                final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
+                final long timeLeft = NotificationThrottleHun.isEnabled()
+                        ? Math.max(finishTime, mEarliestRemovalTime) - now
+                        : Math.max(finishTime - now, mMinimumDisplayTime);
                 return timeLeft;
             };
             scheduleAutoRemovalCallback(finishTimeCalculator, "updateEntry (not sticky)");
@@ -818,13 +834,6 @@
         }
 
         public int compareNonTimeFields(HeadsUpEntry headsUpEntry) {
-            boolean isPinned = mEntry.isRowPinned();
-            boolean otherPinned = headsUpEntry.mEntry.isRowPinned();
-            if (isPinned && !otherPinned) {
-                return -1;
-            } else if (!isPinned && otherPinned) {
-                return 1;
-            }
             boolean selfFullscreen = hasFullScreenIntent(mEntry);
             boolean otherFullscreen = hasFullScreenIntent(headsUpEntry.mEntry);
             if (selfFullscreen && !otherFullscreen) {
@@ -851,6 +860,13 @@
         }
 
         public int compareTo(@NonNull HeadsUpEntry headsUpEntry) {
+            boolean isPinned = mEntry.isRowPinned();
+            boolean otherPinned = headsUpEntry.mEntry.isRowPinned();
+            if (isPinned && !otherPinned) {
+                return -1;
+            } else if (!isPinned && otherPinned) {
+                return 1;
+            }
             int nonTimeCompareResult = compareNonTimeFields(headsUpEntry);
             if (nonTimeCompareResult != 0) {
                 return nonTimeCompareResult;
diff --git a/packages/SystemUI/src/com/android/systemui/theme/CustomDynamicColors.java b/packages/SystemUI/src/com/android/systemui/theme/CustomDynamicColors.java
new file mode 100644
index 0000000..efeb2f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/theme/CustomDynamicColors.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.theme;
+
+import com.google.ux.material.libmonet.dynamiccolor.ContrastCurve;
+import com.google.ux.material.libmonet.dynamiccolor.DynamicColor;
+import com.google.ux.material.libmonet.dynamiccolor.MaterialDynamicColors;
+import com.google.ux.material.libmonet.dynamiccolor.ToneDeltaPair;
+import com.google.ux.material.libmonet.dynamiccolor.TonePolarity;
+
+class CustomDynamicColors {
+    private final MaterialDynamicColors mMdc;
+
+    CustomDynamicColors(boolean isExtendedFidelity) {
+        this.mMdc = new MaterialDynamicColors(isExtendedFidelity);
+    }
+
+    // CLOCK COLORS
+
+    public DynamicColor widgetBackground() {
+        return new DynamicColor(
+                /* name= */ "widget_background",
+                /* palette= */ (s) -> s.primaryPalette,
+                /* tone= */ (s) -> s.isDark ? 20.0 : 95.0,
+                /* isBackground= */ true,
+                /* background= */ null,
+                /* secondBackground= */ null,
+                /* contrastCurve= */ null,
+                /* toneDeltaPair= */ null);
+    }
+
+    public DynamicColor clockHour() {
+        return new DynamicColor(
+                /* name= */ "clock_hour",
+                /* palette= */ (s) -> s.secondaryPalette,
+                /* tone= */ (s) -> s.isDark ? 30.0 : 60.0,
+                /* isBackground= */ false,
+                /* background= */ (s) -> widgetBackground(),
+                /* secondBackground= */ null,
+                /* contrastCurve= */ new ContrastCurve(1.0, 4.0, 5.0, 15.0),
+                /* toneDeltaPair= */
+                (s) -> new ToneDeltaPair(clockHour(), clockMinute(), 10.0, TonePolarity.DARKER,
+                        false));
+    }
+
+    public DynamicColor clockMinute() {
+        return new DynamicColor(
+                /* name= */ "clock_minute",
+                /* palette= */ (s) -> s.primaryPalette,
+                /* tone= */ (s) -> s.isDark ? 40.0 : 90.0,
+                /* isBackground= */ false,
+                /* background= */ (s) -> widgetBackground(),
+                /* secondBackground= */ null,
+                /* contrastCurve= */ new ContrastCurve(1.0, 6.5, 10.0, 15.0),
+                /* toneDeltaPair= */ null);
+    }
+
+    public DynamicColor clockSecond() {
+        return new DynamicColor(
+                /* name= */ "clock_second",
+                /* palette= */ (s) -> s.tertiaryPalette,
+                /* tone= */ (s) -> s.isDark ? 40.0 : 90.0,
+                /* isBackground= */ false,
+                /* background= */ (s) -> widgetBackground(),
+                /* secondBackground= */ null,
+                /* contrastCurve= */ new ContrastCurve(1.0, 5.0, 70.0, 11.0),
+                /* toneDeltaPair= */ null);
+    }
+
+    public DynamicColor weatherTemp() {
+        return new DynamicColor(
+                /* name= */ "clock_second",
+                /* palette= */ (s) -> s.primaryPalette,
+                /* tone= */ (s) -> s.isDark ? 55.0 : 80.0,
+                /* isBackground= */ false,
+                /* background= */ (s) -> widgetBackground(),
+                /* secondBackground= */ null,
+                /* contrastCurve= */ new ContrastCurve(1.0, 5.0, 70.0, 11.0),
+                /* toneDeltaPair= */ null);
+    }
+
+    // THEME APP ICONS
+
+    public DynamicColor themeApp() {
+        return new DynamicColor(
+                /* name= */ "theme_app",
+                /* palette= */ (s) -> s.primaryPalette,
+                /* tone= */ (s) -> s.isDark ? 90.0 : 30.0, // Adjusted values
+                /* isBackground= */ true,
+                /* background= */ null,
+                /* secondBackground= */ null,
+                /* contrastCurve= */ null,
+                /* toneDeltaPair= */ null);
+    }
+
+    public DynamicColor onThemeApp() {
+        return new DynamicColor(
+                /* name= */ "on_theme_app",
+                /* palette= */ (s) -> s.primaryPalette,
+                /* tone= */ (s) -> s.isDark ? 40.0 : 80.0, // Adjusted values
+                /* isBackground= */ false,
+                /* background= */ (s) -> themeApp(),
+                /* secondBackground= */ null,
+                /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 7.0, 10.0),
+                /* toneDeltaPair= */ null);
+    }
+
+    public DynamicColor themeAppRing() {
+        return new DynamicColor(
+                /* name= */ "theme_app_ring",
+                /* palette= */ (s) -> s.primaryPalette,
+                /* tone= */ (s) -> 70.0,
+                /* isBackground= */ true,
+                /* background= */ null,
+                /* secondBackground= */ null,
+                /* contrastCurve= */ new ContrastCurve(1.0, 1.0, 1.0, 1.0),
+                /* toneDeltaPair= */ null);
+    }
+
+    public DynamicColor themeNotif() {
+        return new DynamicColor(
+                /* name= */ "theme_notif",
+                /* palette= */ (s) -> s.tertiaryPalette,
+                /* tone= */ (s) -> s.isDark ? 80.0 : 90.0,
+                /* isBackground= */ false,
+                /* background= */ (s) -> themeAppRing(),
+                /* secondBackground= */ null,
+                /* contrastCurve= */ new ContrastCurve(1.0, 1.0, 1.0, 1.0),
+                /* toneDeltaPair= */
+                (s) -> new ToneDeltaPair(themeNotif(), themeAppRing(), 10.0, TonePolarity.NEARER,
+                        false));
+    }
+
+    // SUPER G COLORS
+
+    public DynamicColor brandA() {
+        return new DynamicColor(
+                /* name= */ "brand_a",
+                /* palette= */ (s) -> s.primaryPalette,
+                /* tone= */ (s) -> s.isDark ? 40.0 : 80.0,
+                /* isBackground= */ true,
+                /* background= */ (s) -> mMdc.surfaceContainerLow(),
+                /* secondBackground= */ null,
+                /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 7.0, 17.0),
+                /* toneDeltaPair= */
+                (s) -> new ToneDeltaPair(brandA(), brandB(), 10.0, TonePolarity.NEARER, false));
+    }
+
+    public DynamicColor brandB() {
+        return new DynamicColor(
+                /* name= */ "brand_b",
+                /* palette= */ (s) -> s.secondaryPalette,
+                /* tone= */ (s) -> s.isDark ? 70.0 : 98.0,
+                /* isBackground= */ true,
+                /* background= */ (s) -> mMdc.surfaceContainerLow(),
+                /* secondBackground= */ null,
+                /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 3.0, 6.0),
+                /* toneDeltaPair= */
+                (s) -> new ToneDeltaPair(brandB(), brandC(), 10.0, TonePolarity.NEARER, false));
+    }
+
+    public DynamicColor brandC() {
+        return new DynamicColor(
+                /* name= */ "brand_c",
+                /* palette= */ (s) -> s.primaryPalette,
+                /* tone= */ (s) -> s.isDark ? 50.0 : 60.0,
+                /* isBackground= */ false,
+                /* background= */ (s) -> mMdc.surfaceContainerLow(),
+                /* secondBackground= */ null,
+                /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 4.0, 9.0),
+                /* toneDeltaPair= */
+                (s) -> new ToneDeltaPair(brandC(), brandD(), 10.0, TonePolarity.NEARER, false));
+    }
+
+    public DynamicColor brandD() {
+        return new DynamicColor(
+                /* name= */ "brand_d",
+                /* palette= */ (s) -> s.tertiaryPalette,
+                /* tone= */ (s) -> s.isDark ? 59.0 : 90.0,
+                /* isBackground= */ false,
+                /* background= */ (s) -> mMdc.surfaceContainerLow(),
+                /* secondBackground= */ null,
+                /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 4.0, 13.0),
+                /* toneDeltaPair= */
+                (s) -> new ToneDeltaPair(brandD(), brandA(), 10.0, TonePolarity.NEARER, false));
+    }
+
+    // QUICK SETTING TIILES
+
+    public DynamicColor underSurface() {
+        return new DynamicColor(
+                /* name= */ "under_surface",
+                /* palette= */ (s) -> s.primaryPalette,
+                /* tone= */ (s) -> 0.0,
+                /* isBackground= */ true,
+                /* background= */ null,
+                /* secondBackground= */ null,
+                /* contrastCurve= */ null,
+                /* toneDeltaPair= */ null);
+    }
+
+    public DynamicColor shadeActive() {
+        return new DynamicColor(
+                /* name= */ "shade_active",
+                /* palette= */ (s) -> s.primaryPalette,
+                /* tone= */ (s) -> 90.0,
+                /* isBackground= */ false,
+                /* background= */ (s) -> underSurface(),
+                /* secondBackground= */ null,
+                /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 4.5, 7.0),
+                /* toneDeltaPair= */
+                (s) -> new ToneDeltaPair(shadeActive(), shadeInactive(), 30.0, TonePolarity.LIGHTER,
+                        false));
+    }
+
+    public DynamicColor onShadeActive() {
+        return new DynamicColor(
+                /* name= */ "on_shade_active",
+                /* palette= */ (s) -> s.primaryPalette,
+                /* tone= */ (s) -> 10.0,
+                /* isBackground= */ false,
+                /* background= */ (s) -> shadeActive(),
+                /* secondBackground= */ null,
+                /* contrastCurve= */ new ContrastCurve(1.0, 4.5, 7.0, 11.0),
+                /* toneDeltaPair= */
+                (s) -> new ToneDeltaPair(onShadeActive(), onShadeActiveVariant(), 20.0,
+                        TonePolarity.NEARER, false));
+    }
+
+    public DynamicColor onShadeActiveVariant() {
+        return new DynamicColor(
+                /* name= */ "on_shade_active_variant",
+                /* palette= */ (s) -> s.primaryPalette,
+                /* tone= */ (s) -> 30.0,
+                /* isBackground= */ false,
+                /* background= */ (s) -> shadeActive(),
+                /* secondBackground= */ null,
+                /* contrastCurve= */ new ContrastCurve(1.0, 4.5, 7.0, 11.0),
+                /* toneDeltaPair= */
+                (s) -> new ToneDeltaPair(onShadeActiveVariant(), onShadeActive(), 20.0,
+                        TonePolarity.NEARER, false));
+    }
+
+    public DynamicColor shadeInactive() {
+        return new DynamicColor(
+                /* name= */ "shade_inactive",
+                /* palette= */ (s) -> s.neutralPalette,
+                /* tone= */ (s) -> 20.0,
+                /* isBackground= */ true,
+                /* background= */ (s) -> underSurface(),
+                /* secondBackground= */ null,
+                /* contrastCurve= */ new ContrastCurve(1.0, 1.0, 1.0, 1.0),
+                /* toneDeltaPair= */(s) -> new ToneDeltaPair(shadeInactive(), shadeDisabled(), 15.0,
+                TonePolarity.LIGHTER, false));
+    }
+
+    public DynamicColor onShadeInactive() {
+        return new DynamicColor(
+                /* name= */ "on_shade_inactive",
+                /* palette= */ (s) -> s.neutralVariantPalette,
+                /* tone= */ (s) -> 90.0,
+                /* isBackground= */ true,
+                /* background= */ (s) -> shadeInactive(),
+                /* secondBackground= */ null,
+                /* contrastCurve= */ new ContrastCurve(1.0, 4.5, 7.0, 11.0),
+                /* toneDeltaPair= */
+                (s) -> new ToneDeltaPair(onShadeInactive(), onShadeInactiveVariant(), 10.0,
+                        TonePolarity.NEARER, false));
+    }
+
+    public DynamicColor onShadeInactiveVariant() {
+        return new DynamicColor(
+                /* name= */ "on_shade_inactive_variant",
+                /* palette= */ (s) -> s.neutralVariantPalette,
+                /* tone= */ (s) -> 80.0,
+                /* isBackground= */ false,
+                /* background= */ (s) -> shadeInactive(),
+                /* secondBackground= */ null,
+                /* contrastCurve= */ new ContrastCurve(1.0, 4.5, 7.0, 11.0),
+                /* toneDeltaPair= */
+                (s) -> new ToneDeltaPair(onShadeInactiveVariant(), onShadeInactive(), 10.0,
+                        TonePolarity.NEARER, false));
+    }
+
+    public DynamicColor shadeDisabled() {
+        return new DynamicColor(
+                /* name= */ "shade_disabled",
+                /* palette= */ (s) -> s.neutralPalette,
+                /* tone= */ (s) -> 4.0,
+                /* isBackground= */ false,
+                /* background= */ (s) -> underSurface(),
+                /* secondBackground= */ null,
+                /* contrastCurve= */ new ContrastCurve(1.0, 1.0, 1.0, 1.0),
+                /* toneDeltaPair= */ null);
+    }
+
+    public DynamicColor overviewBackground() {
+        return new DynamicColor(
+                /* name= */ "overview_background",
+                /* palette= */ (s) -> s.neutralVariantPalette,
+                /* tone= */ (s) -> s.isDark ? 80.0 : 35.0,
+                /* isBackground= */ true,
+                /* background= */ null,
+                /* secondBackground= */ null,
+                /* contrastCurve= */null,
+                /* toneDeltaPair= */ null);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
index a983d2f..3518759 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
+++ b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
@@ -103,5 +103,33 @@
                 Pair.create("on_tertiary_fixed_variant", mdc.onTertiaryFixedVariant()),
             )
         }
+
+        @JvmStatic
+        fun getCustomColorsMapped(isExtendedFidelity: Boolean): List<Pair<String, DynamicColor>> {
+            val customMdc = CustomDynamicColors(isExtendedFidelity)
+            return arrayListOf(
+                Pair.create("widget_background", customMdc.widgetBackground()),
+                Pair.create("clock_hour", customMdc.clockHour()),
+                Pair.create("clock_minute", customMdc.clockMinute()),
+                Pair.create("clock_second", customMdc.weatherTemp()),
+                Pair.create("theme_app", customMdc.themeApp()),
+                Pair.create("on_theme_app", customMdc.onThemeApp()),
+                Pair.create("theme_app_ring", customMdc.themeAppRing()),
+                Pair.create("on_theme_app_ring", customMdc.themeNotif()),
+                Pair.create("brand_a", customMdc.brandA()),
+                Pair.create("brand_b", customMdc.brandB()),
+                Pair.create("brand_c", customMdc.brandC()),
+                Pair.create("brand_d", customMdc.brandD()),
+                Pair.create("under_surface", customMdc.underSurface()),
+                Pair.create("shade_active", customMdc.shadeActive()),
+                Pair.create("on_shade_active", customMdc.onShadeActive()),
+                Pair.create("on_shade_active_variant", customMdc.onShadeActiveVariant()),
+                Pair.create("shade_inactive", customMdc.shadeInactive()),
+                Pair.create("on_shade_inactive", customMdc.onShadeInactive()),
+                Pair.create("on_shade_inactive_variant", customMdc.onShadeInactiveVariant()),
+                Pair.create("shade_disabled", customMdc.shadeDisabled()),
+                Pair.create("overview_background", customMdc.overviewBackground())
+            )
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 5c3bbb7..d256c4a 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -56,6 +56,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Pair;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
@@ -84,6 +85,7 @@
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.SecureSettings;
 
+import com.google.ux.material.libmonet.dynamiccolor.DynamicColor;
 import com.google.ux.material.libmonet.dynamiccolor.MaterialDynamicColors;
 
 import org.json.JSONException;
@@ -631,29 +633,33 @@
 
     protected FabricatedOverlay createDynamicOverlay() {
         FabricatedOverlay overlay = newFabricatedOverlay("dynamic");
-        assignDynamicPaletteToOverlay(overlay, true /* isDark */);
-        assignDynamicPaletteToOverlay(overlay, false /* isDark */);
-        assignFixedColorsToOverlay(overlay);
+        //Themed Colors
+        assignColorsToOverlay(overlay, DynamicColors.allDynamicColorsMapped(mIsFidelityEnabled),
+                false);
+        // Fixed Colors
+        assignColorsToOverlay(overlay, DynamicColors.getFixedColorsMapped(mIsFidelityEnabled),
+                true);
+        //Custom Colors
+        assignColorsToOverlay(overlay, DynamicColors.getCustomColorsMapped(mIsFidelityEnabled),
+                false);
         return overlay;
     }
 
-    private void assignDynamicPaletteToOverlay(FabricatedOverlay overlay, boolean isDark) {
-        String suffix = isDark ? "dark" : "light";
-        ColorScheme scheme = isDark ? mDarkColorScheme : mLightColorScheme;
-        DynamicColors.allDynamicColorsMapped(mIsFidelityEnabled).forEach(p -> {
-            String resourceName = "android:color/system_" + p.first + "_" + suffix;
-            int colorValue = p.second.getArgb(scheme.getMaterialScheme());
-            overlay.setResourceValue(resourceName, TYPE_INT_COLOR_ARGB8, colorValue,
-                    null /* configuration */);
-        });
-    }
+    private void assignColorsToOverlay(FabricatedOverlay overlay,
+            List<Pair<String, DynamicColor>> colors, Boolean isFixed) {
+        colors.forEach(p -> {
+            String prefix = "android:color/system_" + p.first;
 
-    private void assignFixedColorsToOverlay(FabricatedOverlay overlay) {
-        DynamicColors.getFixedColorsMapped(mIsFidelityEnabled).forEach(p -> {
-            String resourceName = "android:color/system_" + p.first;
-            int colorValue = p.second.getArgb(mLightColorScheme.getMaterialScheme());
-            overlay.setResourceValue(resourceName, TYPE_INT_COLOR_ARGB8, colorValue,
-                    null /* configuration */);
+            if (isFixed) {
+                overlay.setResourceValue(prefix, TYPE_INT_COLOR_ARGB8,
+                        p.second.getArgb(mLightColorScheme.getMaterialScheme()), null);
+                return;
+            }
+
+            overlay.setResourceValue(prefix + "_light", TYPE_INT_COLOR_ARGB8,
+                    p.second.getArgb(mLightColorScheme.getMaterialScheme()), null);
+            overlay.setResourceValue(prefix + "_dark", TYPE_INT_COLOR_ARGB8,
+                    p.second.getArgb(mDarkColorScheme.getMaterialScheme()), null);
         });
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt
index ca5ea3b..135edfc 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt
@@ -32,6 +32,7 @@
 import android.view.SurfaceSession
 import android.view.WindowManager
 import android.view.WindowlessWindowManager
+import androidx.annotation.WorkerThread
 import com.android.app.tracing.traceSection
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -235,8 +236,10 @@
     }
 
     private inner class RotationWatcher : RotationChangeProvider.RotationListener {
+        @WorkerThread
         override fun onRotationChanged(newRotation: Int) {
             traceSection("$TAG#onRotationChanged") {
+                ensureInBackground()
                 if (currentRotation != newRotation) {
                     currentRotation = newRotation
                     scrimView?.revealEffect = lightRevealEffectFactory(currentRotation)
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 37be1c6..a817b31 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.user.data.repository
 
 import android.annotation.SuppressLint
+import android.annotation.UserIdInt
 import android.content.Context
 import android.content.pm.UserInfo
 import android.os.UserHandle
@@ -107,6 +108,22 @@
     fun isSimpleUserSwitcher(): Boolean
 
     fun isUserSwitcherEnabled(): Boolean
+
+    /**
+     * Returns the user ID of the "main user" of the device. This user may have access to certain
+     * features which are limited to at most one user. There will never be more than one main user
+     * on a device.
+     *
+     * <p>Currently, on most form factors the first human user on the device will be the main user;
+     * in the future, the concept may be transferable, so a different user (or even no user at all)
+     * may be designated the main user instead. On other form factors there might not be a main
+     * user.
+     *
+     * <p> When the device doesn't have a main user, this will return {@code null}.
+     *
+     * @see [UserManager.getMainUser]
+     */
+    @UserIdInt suspend fun getMainUserId(): Int?
 }
 
 @SysUISingleton
@@ -239,6 +256,10 @@
         return _userSwitcherSettings.value.isUserSwitcherEnabled
     }
 
+    override suspend fun getMainUserId(): Int? {
+        return withContext(backgroundDispatcher) { manager.mainUser?.identifier }
+    }
+
     private suspend fun getSettings(): UserSwitcherSettingsModel {
         return withContext(backgroundDispatcher) {
             val isSimpleUserSwitcher =
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
index bfed0c4..0a1724c 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
@@ -70,8 +70,10 @@
     val isGuestUserResetting: Boolean = repository.isGuestUserResetting
 
     init {
-        resumeSessionReceiver.register()
-        resetOrExitSessionReceiver.register()
+        if (applicationContext.userId == UserHandle.USER_SYSTEM) {
+            resumeSessionReceiver.register()
+            resetOrExitSessionReceiver.register()
+        }
     }
 
     /** Notifies that the device has finished booting. */
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
index 38b381a..59c819d 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
@@ -2,6 +2,7 @@
 
 import android.annotation.UserIdInt
 import android.content.pm.UserInfo
+import android.os.UserManager
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.Flags.refactorGetCurrentUser
 import com.android.systemui.dagger.SysUISingleton
@@ -38,4 +39,23 @@
             KeyguardUpdateMonitor.getCurrentUser()
         }
     }
+
+    /**
+     * Returns the user ID of the "main user" of the device. This user may have access to certain
+     * features which are limited to at most one user. There will never be more than one main user
+     * on a device.
+     *
+     * <p>Currently, on most form factors the first human user on the device will be the main user;
+     * in the future, the concept may be transferable, so a different user (or even no user at all)
+     * may be designated the main user instead. On other form factors there might not be a main
+     * user.
+     *
+     * <p> When the device doesn't have a main user, this will return {@code null}.
+     *
+     * @see [UserManager.getMainUser]
+     */
+    @UserIdInt
+    fun getMainUserId(): Int? {
+        return repository.mainUserId
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
index 9339651..516cb46 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
@@ -533,7 +533,7 @@
                 targetUserId = targetUserId,
                 ::showDialog,
                 ::dismissDialog,
-                ::selectUser,
+                ::switchUser
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt
index b300885..a2759c6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt
@@ -28,11 +28,23 @@
      *
      * Usage:
      * ```
-     * val result = and(flow1, flow2)
+     * val result = allOf(flow1, flow2)
      * ```
      */
-    fun and(vararg flows: Flow<Boolean>): Flow<Boolean> =
-        combine(flows.asIterable()) { values -> values.all { it } }.distinctUntilChanged()
+    fun allOf(vararg flows: Flow<Boolean>): Flow<Boolean> = flows.asIterable().all()
+
+    /**
+     * Logical AND operator for boolean flows. Will collect all flows and [combine] them to
+     * determine the result.
+     */
+    fun Array<Flow<Boolean>>.all(): Flow<Boolean> = allOf(*this)
+
+    /**
+     * Logical AND operator for boolean flows. Will collect all flows and [combine] them to
+     * determine the result.
+     */
+    fun Iterable<Flow<Boolean>>.all(): Flow<Boolean> =
+        combine(this) { values -> values.all { it } }.distinctUntilChanged()
 
     /**
      * Logical NOT operator for a boolean flow.
@@ -48,6 +60,36 @@
      * Logical OR operator for a boolean flow. Will collect all flows and [combine] them to
      * determine the result.
      */
-    fun or(vararg flows: Flow<Boolean>): Flow<Boolean> =
-        combine(flows.asIterable()) { values -> values.any { it } }.distinctUntilChanged()
+    fun anyOf(vararg flows: Flow<Boolean>): Flow<Boolean> = flows.asIterable().any()
+
+    /**
+     * Logical OR operator for a boolean flow. Will collect all flows and [combine] them to
+     * determine the result.
+     */
+    fun Array<Flow<Boolean>>.any(): Flow<Boolean> = anyOf(*this)
+
+    /**
+     * Logical OR operator for a boolean flow. Will collect all flows and [combine] them to
+     * determine the result.
+     */
+    fun Iterable<Flow<Boolean>>.any(): Flow<Boolean> =
+        combine(this) { values -> values.any { it } }.distinctUntilChanged()
+
+    /**
+     * Returns a Flow that produces `true` when all input flows are producing `false`, otherwise
+     * produces `false`.
+     */
+    fun noneOf(vararg flows: Flow<Boolean>): Flow<Boolean> = not(anyOf(*flows))
+
+    /**
+     * Returns a Flow that produces `true` when all input flows are producing `false`, otherwise
+     * produces `false`.
+     */
+    fun Array<Flow<Boolean>>.none(): Flow<Boolean> = noneOf(*this)
+
+    /**
+     * Returns a Flow that produces `true` when all input flows are producing `false`, otherwise
+     * produces `false`.
+     */
+    fun Iterable<Flow<Boolean>>.none(): Flow<Boolean> = not(any())
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 46ce5f2..1ec86a4 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -97,3 +97,12 @@
 fun <A, B, R> combineFlows(flow1: Flow<A>, flow2: Flow<B>, bifunction: (A, B) -> R): Flow<R> {
     return combine(flow1, flow2, bifunction)
 }
+
+fun <A, B, C, R> combineFlows(
+    flow1: Flow<A>,
+    flow2: Flow<B>,
+    flow3: Flow<C>,
+    trifunction: (A, B, C) -> R
+): Flow<R> {
+    return combine(flow1, flow2, flow3, trifunction)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/LocationControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/LocationControllerExt.kt
new file mode 100644
index 0000000..ee1b565
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/LocationControllerExt.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.util.kotlin
+
+import com.android.systemui.statusbar.policy.LocationController
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.onStart
+
+fun LocationController.isLocationEnabledFlow(): Flow<Boolean> {
+    return conflatedCallbackFlow {
+            val locationCallback =
+                object : LocationController.LocationChangeCallback {
+                    override fun onLocationSettingsChanged(locationEnabled: Boolean) {
+                        trySend(locationEnabled)
+                    }
+                }
+            addCallback(locationCallback)
+            awaitClose { removeCallback(locationCallback) }
+        }
+        .onStart { emit(isLocationEnabled) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt
index ab6a37b..d9e19d8 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt
@@ -17,23 +17,15 @@
 package com.android.systemui.util.kotlin
 
 import android.content.SharedPreferences
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.mapNotNull
 
 object SharedPreferencesExt {
-    /**
-     * Returns a flow of [Unit] that is invoked each time shared preference is updated.
-     *
-     * @param key Optional key to limit updates to a particular key.
-     */
-    fun SharedPreferences.observe(key: String? = null): Flow<Unit> =
-        conflatedCallbackFlow {
-                val listener =
-                    SharedPreferences.OnSharedPreferenceChangeListener { _, key -> trySend(key) }
-                registerOnSharedPreferenceChangeListener(listener)
-                awaitClose { unregisterOnSharedPreferenceChangeListener(listener) }
-            }
-            .mapNotNull { changedKey -> if ((key ?: changedKey) == changedKey) Unit else null }
+    /** Returns a flow of [Unit] that is invoked each time shared preference is updated. */
+    fun SharedPreferences.observe(): Flow<Unit> = conflatedCallbackFlow {
+        val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> trySend(Unit) }
+        registerOnSharedPreferenceChangeListener(listener)
+        awaitClose { unregisterOnSharedPreferenceChangeListener(listener) }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index c69fb66..e56893a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -174,9 +174,6 @@
     private static final String TYPE_DISMISS = "dismiss";
     /** Volume dialog slider animation. */
     private static final String TYPE_UPDATE = "update";
-    static final int PROGRESS_HAPTICS_DISABLED = 0;
-    static final int PROGRESS_HAPTICS_EAGER = 1;
-    static final int PROGRESS_HAPTICS_ANIMATED = 2;
 
     /**
      *  TODO(b/290612381): remove lingering animations or tolerate them
@@ -285,7 +282,7 @@
     @GuardedBy("mSafetyWarningLock")
     private CsdWarningDialog mCsdDialog;
     private boolean mHovering = false;
-    private final boolean mShowActiveStreamOnly;
+    private final boolean mIsTv;
     private boolean mConfigChanged = false;
     private boolean mIsAnimatingDismiss = false;
     private boolean mHasSeenODICaptionsTooltip;
@@ -346,7 +343,7 @@
         mConfigurationController = configurationController;
         mMediaOutputDialogManager = mediaOutputDialogManager;
         mCsdWarningDialogFactory = csdWarningDialogFactory;
-        mShowActiveStreamOnly = showActiveStreamOnly();
+        mIsTv = isTv();
         mHasSeenODICaptionsTooltip =
                 Prefs.getBoolean(context, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false);
         mShowLowMediaVolumeIcon =
@@ -1635,7 +1632,7 @@
         Trace.endSection();
     }
 
-    private boolean showActiveStreamOnly() {
+    private boolean isTv() {
         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)
                 || mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION);
     }
@@ -1647,7 +1644,7 @@
             return true;
         }
 
-        if (!mShowActiveStreamOnly) {
+        if (!mIsTv) {
             if (row.stream == AudioSystem.STREAM_ACCESSIBILITY) {
                 return mShowA11yStream;
             }
@@ -2092,6 +2089,11 @@
         }
         final int newProgress = getProgressFromVolume(row.ss, row.slider, vlevel);
         if (progress != newProgress) {
+            if (mIsTv) {
+                // don't animate slider on TVs
+                row.slider.setProgress(newProgress, false);
+                return;
+            }
             if (mShowing && rowVisible) {
                 // animate!
                 if (row.anim != null && row.anim.isRunning()
@@ -2112,7 +2114,7 @@
                     row.anim.setIntValues(progress, newProgress);
                     // The animator can't keep up with the volume changes so haptics need to be
                     // triggered here. This happens when the volume keys are continuously pressed.
-                    row.deliverOnProgressChangedHaptics(false, newProgress, PROGRESS_HAPTICS_EAGER);
+                    row.deliverOnProgressChangedHaptics(false, newProgress);
                 }
                 row.animTargetProgress = newProgress;
                 row.anim.setDuration(UPDATE_ANIMATION_DURATION);
@@ -2127,13 +2129,14 @@
         }
     }
 
-    @VisibleForTesting int progressHapticsForStream(int stream) {
+    @VisibleForTesting
+    boolean canDeliverProgressHapticsToStream(int stream, boolean fromUser, int progress) {
         for (VolumeRow row: mRows) {
             if (row.stream == stream) {
-                return row.mProgressHapticsType;
+                return row.deliverOnProgressChangedHaptics(fromUser, progress);
             }
         }
-        return PROGRESS_HAPTICS_DISABLED;
+        return false;
     }
 
     private void recheckH(VolumeRow row) {
@@ -2527,8 +2530,7 @@
                 if (fromUser || mRow.animTargetProgress == progress) {
                     // Deliver user-generated slider haptics immediately, or when the animation
                     // completes
-                    mRow.deliverOnProgressChangedHaptics(
-                            fromUser, progress, PROGRESS_HAPTICS_ANIMATED);
+                    mRow.deliverOnProgressChangedHaptics(fromUser, progress);
                 }
             }
             if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream)
@@ -2641,7 +2643,6 @@
         private int animTargetProgress;
         private int lastAudibleLevel = 1;
         private SeekbarHapticPlugin mHapticPlugin;
-        private int mProgressHapticsType = PROGRESS_HAPTICS_DISABLED;
 
         void setIcon(int iconRes, Resources.Theme theme) {
             if (icon != null) {
@@ -2683,15 +2684,23 @@
             slider.setOnTouchListener(null);
         }
 
-        void deliverOnProgressChangedHaptics(boolean fromUser, int progress, int hapticsType) {
-            if (mHapticPlugin == null) return;
+        /**
+         * Deliver haptics when the progress of the slider has changed.
+         *
+         * @param fromUser True if the progress changed was caused by the user.
+         * @param progress The progress value of the slider.
+         * @return True if haptics were successfully delivered. False otherwise. This will happen
+         *   if mHapticPlugin is null
+         */
+        boolean deliverOnProgressChangedHaptics(boolean fromUser, int progress) {
+            if (mHapticPlugin == null) return false;
 
             mHapticPlugin.onProgressChanged(slider, progress, fromUser);
             if (!fromUser) {
                 // Consider a change from program as the volume key being continuously pressed
                 mHapticPlugin.onKeyDown();
             }
-            mProgressHapticsType = hapticsType;
+            return true;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
index 155102c9..3696108 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
@@ -27,6 +27,8 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
 import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactoryImpl
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaControllerInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaControllerInteractorImpl
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
@@ -41,6 +43,11 @@
         impl: LocalMediaRepositoryFactoryImpl
     ): LocalMediaRepositoryFactory
 
+    @Binds
+    fun bindMediaControllerInteractor(
+        impl: MediaControllerInteractorImpl
+    ): MediaControllerInteractor
+
     companion object {
 
         @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt
index 8ce3b1f..e1787e8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt
@@ -21,21 +21,15 @@
 import androidx.slice.Slice
 import androidx.slice.SliceViewManager
 import com.android.settingslib.bluetooth.BluetoothUtils
-import com.android.settingslib.media.BluetoothMediaDevice
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.slice.sliceForUri
-import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
 
 /** Provides ANC slice data */
 interface AncSliceRepository {
@@ -48,34 +42,30 @@
      * - there is no supported device connected;
      * - there is no slice provider for the uri;
      */
-    fun ancSlice(width: Int, isCollapsed: Boolean, hideLabel: Boolean): Flow<Slice?>
+    fun ancSlice(
+        device: BluetoothDevice,
+        width: Int,
+        isCollapsed: Boolean,
+        hideLabel: Boolean
+    ): Flow<Slice?>
 }
 
-@OptIn(ExperimentalCoroutinesApi::class)
 class AncSliceRepositoryImpl
 @AssistedInject
 constructor(
-    mediaRepositoryFactory: LocalMediaRepositoryFactory,
-    @Background private val backgroundCoroutineContext: CoroutineContext,
+    @Main private val mainCoroutineContext: CoroutineContext,
     @Assisted private val sliceViewManager: SliceViewManager,
 ) : AncSliceRepository {
 
-    private val localMediaRepository = mediaRepositoryFactory.create(null)
-
-    override fun ancSlice(width: Int, isCollapsed: Boolean, hideLabel: Boolean): Flow<Slice?> {
-        return localMediaRepository.currentConnectedDevice
-            .map {
-                (it as? BluetoothMediaDevice)
-                    ?.cachedDevice
-                    ?.device
-                    ?.getExtraControlUri(width, isCollapsed, hideLabel)
-            }
-            .distinctUntilChanged()
-            .flatMapLatest { sliceUri ->
-                sliceUri ?: return@flatMapLatest flowOf(null)
-                sliceViewManager.sliceForUri(sliceUri)
-            }
-            .flowOn(backgroundCoroutineContext)
+    override fun ancSlice(
+        device: BluetoothDevice,
+        width: Int,
+        isCollapsed: Boolean,
+        hideLabel: Boolean
+    ): Flow<Slice?> {
+        val sliceUri =
+            device.getExtraControlUri(width, isCollapsed, hideLabel) ?: return flowOf(null)
+        return sliceViewManager.sliceForUri(sliceUri).flowOn(mainCoroutineContext)
     }
 
     private fun BluetoothDevice.getExtraControlUri(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt
index cefa269..cfff457 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt
@@ -19,6 +19,8 @@
 import android.app.slice.Slice.HINT_ERROR
 import android.app.slice.SliceItem.FORMAT_SLICE
 import androidx.slice.Slice
+import com.android.systemui.volume.domain.interactor.AudioOutputInteractor
+import com.android.systemui.volume.domain.model.AudioOutputDevice
 import com.android.systemui.volume.panel.component.anc.data.repository.AncSliceRepository
 import com.android.systemui.volume.panel.component.anc.domain.model.AncSlices
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
@@ -32,6 +34,7 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.stateIn
 
 /** Provides a valid slice from [AncSliceRepository]. */
@@ -40,6 +43,7 @@
 class AncSliceInteractor
 @Inject
 constructor(
+    private val audioOutputInteractor: AudioOutputInteractor,
     private val ancSliceRepository: AncSliceRepository,
     scope: CoroutineScope,
 ) {
@@ -70,9 +74,20 @@
      * remove the labels from the [Slice].
      */
     private fun ancSlice(width: Int, isCollapsed: Boolean, hideLabel: Boolean): Flow<Slice?> {
-        return ancSliceRepository
-            .ancSlice(width = width, isCollapsed = isCollapsed, hideLabel = hideLabel)
-            .filter { it?.isValidSlice() != false }
+        return audioOutputInteractor.currentAudioDevice.flatMapLatest { outputDevice ->
+            if (outputDevice is AudioOutputDevice.Bluetooth) {
+                ancSliceRepository
+                    .ancSlice(
+                        device = outputDevice.cachedBluetoothDevice.device,
+                        width = width,
+                        isCollapsed = isCollapsed,
+                        hideLabel = hideLabel,
+                    )
+                    .filter { it?.isValidSlice() != false }
+            } else {
+                flowOf(null)
+            }
+        }
     }
 
     private fun Slice.isValidSlice(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt
index bee79bb..c980eb4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.volume.panel.component.anc.ui.viewmodel
 
+import android.content.Intent
 import androidx.slice.Slice
+import androidx.slice.SliceItem
 import com.android.systemui.volume.panel.component.anc.domain.AncAvailabilityCriteria
 import com.android.systemui.volume.panel.component.anc.domain.interactor.AncSliceInteractor
 import com.android.systemui.volume.panel.component.anc.domain.model.AncSlices
@@ -59,6 +61,31 @@
             .map { it.buttonSlice }
             .stateIn(coroutineScope, SharingStarted.Eagerly, null)
 
+    fun isClickable(slice: Slice?): Boolean {
+        slice ?: return false
+        val slices = ArrayDeque<SliceItem>()
+        slices.addAll(slice.items)
+        while (slices.isNotEmpty()) {
+            val item: SliceItem = slices.removeFirst()
+            when (item.format) {
+                android.app.slice.SliceItem.FORMAT_ACTION -> {
+                    val itemActionIntent: Intent? = item.action?.intent
+                    if (itemActionIntent?.hasExtra(EXTRA_ANC_ENABLED) == true) {
+                        return itemActionIntent.getBooleanExtra(EXTRA_ANC_ENABLED, true)
+                    }
+                }
+                android.app.slice.SliceItem.FORMAT_SLICE -> {
+                    item.slice?.items?.let(slices::addAll)
+                }
+            }
+        }
+        return true
+    }
+
+    private companion object {
+        const val EXTRA_ANC_ENABLED = "EXTRA_ANC_ENABLED"
+    }
+
     /** Call this to update [popupSlice] width in a reaction to container size change. */
     fun onPopupSliceWidthChanged(width: Int) {
         interactor.onPopupSliceWidthChanged(width)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
new file mode 100644
index 0000000..4812765
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.panel.component.mediaoutput.domain.interactor
+
+import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
+import android.os.Bundle
+import android.os.Handler
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel
+import javax.inject.Inject
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
+
+interface MediaControllerInteractor {
+
+    /** [MediaController.Callback] flow representation. */
+    fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel>
+}
+
+@SysUISingleton
+class MediaControllerInteractorImpl
+@Inject
+constructor(
+    @Background private val backgroundHandler: Handler,
+) : MediaControllerInteractor {
+
+    override fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel> {
+        return conflatedCallbackFlow {
+            val callback = MediaControllerCallbackProducer(this)
+            mediaController.registerCallback(callback, backgroundHandler)
+            awaitClose { mediaController.unregisterCallback(callback) }
+        }
+    }
+}
+
+private class MediaControllerCallbackProducer(
+    private val producingScope: ProducerScope<MediaControllerChangeModel>
+) : MediaController.Callback() {
+
+    override fun onSessionDestroyed() {
+        send(MediaControllerChangeModel.SessionDestroyed)
+    }
+
+    override fun onSessionEvent(event: String, extras: Bundle?) {
+        send(MediaControllerChangeModel.SessionEvent(event, extras))
+    }
+
+    override fun onPlaybackStateChanged(state: PlaybackState?) {
+        send(MediaControllerChangeModel.PlaybackStateChanged(state))
+    }
+
+    override fun onMetadataChanged(metadata: MediaMetadata?) {
+        send(MediaControllerChangeModel.MetadataChanged(metadata))
+    }
+
+    override fun onQueueChanged(queue: MutableList<MediaSession.QueueItem>?) {
+        send(MediaControllerChangeModel.QueueChanged(queue))
+    }
+
+    override fun onQueueTitleChanged(title: CharSequence?) {
+        send(MediaControllerChangeModel.QueueTitleChanged(title))
+    }
+
+    override fun onExtrasChanged(extras: Bundle?) {
+        send(MediaControllerChangeModel.ExtrasChanged(extras))
+    }
+
+    override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) {
+        send(MediaControllerChangeModel.AudioInfoChanged(info))
+    }
+
+    private fun send(change: MediaControllerChangeModel) {
+        producingScope.launch { producingScope.send(change) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
index dc73344..599bd73 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
@@ -18,11 +18,9 @@
 
 import android.media.session.MediaController
 import android.media.session.PlaybackState
-import android.os.Handler
-import com.android.settingslib.volume.data.repository.MediaControllerChange
 import com.android.settingslib.volume.data.repository.MediaControllerRepository
-import com.android.settingslib.volume.data.repository.stateChanges
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel
 import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import javax.inject.Inject
@@ -45,38 +43,39 @@
 @Inject
 constructor(
     @Background private val backgroundCoroutineContext: CoroutineContext,
-    @Background private val backgroundHandler: Handler,
+    private val mediaControllerInteractor: MediaControllerInteractor,
     private val mediaControllerRepository: MediaControllerRepository,
 ) {
 
     /** [PlaybackState] changes for the [MediaDeviceSession]. */
     fun playbackState(session: MediaDeviceSession): Flow<PlaybackState?> {
         return stateChanges(session) {
-                emit(MediaControllerChange.PlaybackStateChanged(it.playbackState))
+                emit(MediaControllerChangeModel.PlaybackStateChanged(it.playbackState))
             }
-            .filterIsInstance(MediaControllerChange.PlaybackStateChanged::class)
+            .filterIsInstance(MediaControllerChangeModel.PlaybackStateChanged::class)
             .map { it.state }
     }
 
     /** [MediaController.PlaybackInfo] changes for the [MediaDeviceSession]. */
     fun playbackInfo(session: MediaDeviceSession): Flow<MediaController.PlaybackInfo?> {
         return stateChanges(session) {
-                emit(MediaControllerChange.AudioInfoChanged(it.playbackInfo))
+                emit(MediaControllerChangeModel.AudioInfoChanged(it.playbackInfo))
             }
-            .filterIsInstance(MediaControllerChange.AudioInfoChanged::class)
+            .filterIsInstance(MediaControllerChangeModel.AudioInfoChanged::class)
             .map { it.info }
     }
 
     private fun stateChanges(
         session: MediaDeviceSession,
-        onStart: suspend FlowCollector<MediaControllerChange>.(controller: MediaController) -> Unit,
-    ): Flow<MediaControllerChange?> =
+        onStart:
+            suspend FlowCollector<MediaControllerChangeModel>.(controller: MediaController) -> Unit,
+    ): Flow<MediaControllerChangeModel?> =
         mediaControllerRepository.activeSessions
             .flatMapLatest { controllers ->
                 val controller: MediaController =
                     findControllerForSession(controllers, session)
                         ?: return@flatMapLatest flowOf(null)
-                controller.stateChanges(backgroundHandler).onStart { onStart(controller) }
+                mediaControllerInteractor.stateChanges(controller).onStart { onStart(controller) }
             }
             .flowOn(backgroundCoroutineContext)
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
index b00829e..9fbd79a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
@@ -19,12 +19,10 @@
 import android.content.pm.PackageManager
 import android.media.VolumeProvider
 import android.media.session.MediaController
-import android.os.Handler
 import android.util.Log
 import com.android.settingslib.media.MediaDevice
 import com.android.settingslib.volume.data.repository.LocalMediaRepository
 import com.android.settingslib.volume.data.repository.MediaControllerRepository
-import com.android.settingslib.volume.data.repository.stateChanges
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
 import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSessions
@@ -61,7 +59,7 @@
     @VolumePanelScope private val coroutineScope: CoroutineScope,
     @Background private val backgroundCoroutineContext: CoroutineContext,
     mediaControllerRepository: MediaControllerRepository,
-    @Background private val backgroundHandler: Handler,
+    private val mediaControllerInteractor: MediaControllerInteractor,
 ) {
 
     private val activeMediaControllers: Flow<MediaControllers> =
@@ -194,7 +192,10 @@
             return flowOf(null)
         }
 
-        return stateChanges(backgroundHandler).map { this }.onStart { emit(this@stateChanges) }
+        return mediaControllerInteractor
+            .stateChanges(this)
+            .map { this }
+            .onStart { emit(this@stateChanges) }
     }
 
     private data class MediaControllers(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt
new file mode 100644
index 0000000..ef5a44a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.panel.component.mediaoutput.domain.model
+
+import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
+import android.os.Bundle
+
+/** Models particular change event received by [MediaController.Callback]. */
+sealed interface MediaControllerChangeModel {
+
+    data object SessionDestroyed : MediaControllerChangeModel
+
+    data class SessionEvent(val event: String, val extras: Bundle?) : MediaControllerChangeModel
+
+    data class PlaybackStateChanged(val state: PlaybackState?) : MediaControllerChangeModel
+
+    data class MetadataChanged(val metadata: MediaMetadata?) : MediaControllerChangeModel
+
+    data class QueueChanged(val queue: MutableList<MediaSession.QueueItem>?) :
+        MediaControllerChangeModel
+
+    data class QueueTitleChanged(val title: CharSequence?) : MediaControllerChangeModel
+
+    data class ExtrasChanged(val extras: Bundle?) : MediaControllerChangeModel
+
+    data class AudioInfoChanged(val info: MediaController.PlaybackInfo?) :
+        MediaControllerChangeModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
index 298ca67..9ca50d6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
@@ -18,11 +18,9 @@
 
 import android.media.AudioDeviceAttributes
 import android.media.AudioDeviceInfo
-import com.android.settingslib.media.BluetoothMediaDevice
-import com.android.settingslib.media.MediaDevice
-import com.android.settingslib.media.PhoneMediaDevice
 import com.android.settingslib.media.domain.interactor.SpatializerInteractor
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
+import com.android.systemui.volume.domain.interactor.AudioOutputInteractor
+import com.android.systemui.volume.domain.model.AudioOutputDevice
 import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
 import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
@@ -46,16 +44,20 @@
 class SpatialAudioComponentInteractor
 @Inject
 constructor(
-    mediaOutputInteractor: MediaOutputInteractor,
+    audioOutputInteractor: AudioOutputInteractor,
     private val spatializerInteractor: SpatializerInteractor,
     @VolumePanelScope private val coroutineScope: CoroutineScope,
 ) {
 
     private val changes = MutableSharedFlow<Unit>()
     private val currentAudioDeviceAttributes: StateFlow<AudioDeviceAttributes?> =
-        mediaOutputInteractor.currentConnectedDevice
-            .map { mediaDevice ->
-                if (mediaDevice == null) builtinSpeaker else mediaDevice.getAudioDeviceAttributes()
+        audioOutputInteractor.currentAudioDevice
+            .map { audioDevice ->
+                if (audioDevice is AudioOutputDevice.Unknown) {
+                    builtinSpeaker
+                } else {
+                    audioDevice.getAudioDeviceAttributes()
+                }
             }
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), builtinSpeaker)
 
@@ -135,36 +137,35 @@
         changes.emit(Unit)
     }
 
-    private suspend fun MediaDevice.getAudioDeviceAttributes(): AudioDeviceAttributes? {
+    private suspend fun AudioOutputDevice.getAudioDeviceAttributes(): AudioDeviceAttributes? {
         when (this) {
-            is PhoneMediaDevice -> return builtinSpeaker
-            is BluetoothMediaDevice -> {
-                val device = cachedDevice ?: return null
+            is AudioOutputDevice.BuiltIn -> return builtinSpeaker
+            is AudioOutputDevice.Bluetooth -> {
                 return listOf(
                         AudioDeviceAttributes(
                             AudioDeviceAttributes.ROLE_OUTPUT,
                             AudioDeviceInfo.TYPE_BLE_HEADSET,
-                            device.address,
+                            cachedBluetoothDevice.address,
                         ),
                         AudioDeviceAttributes(
                             AudioDeviceAttributes.ROLE_OUTPUT,
                             AudioDeviceInfo.TYPE_BLE_SPEAKER,
-                            device.address,
+                            cachedBluetoothDevice.address,
                         ),
                         AudioDeviceAttributes(
                             AudioDeviceAttributes.ROLE_OUTPUT,
                             AudioDeviceInfo.TYPE_BLE_BROADCAST,
-                            device.address,
+                            cachedBluetoothDevice.address,
                         ),
                         AudioDeviceAttributes(
                             AudioDeviceAttributes.ROLE_OUTPUT,
                             AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
-                            device.address,
+                            cachedBluetoothDevice.address,
                         ),
                         AudioDeviceAttributes(
                             AudioDeviceAttributes.ROLE_OUTPUT,
                             AudioDeviceInfo.TYPE_HEARING_AID,
-                            device.address,
+                            cachedBluetoothDevice.address,
                         )
                     )
                     .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index 0386338..c08cd64 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -49,6 +49,11 @@
     private val uiEventLogger: UiEventLogger,
 ) : SliderViewModel {
 
+    private val streamsAffectedByRing =
+        setOf(
+            AudioManager.STREAM_RING,
+            AudioManager.STREAM_NOTIFICATION,
+        )
     private val audioStream = audioStreamWrapper.audioStream
     private val iconsByStream =
         mapOf(
@@ -125,15 +130,42 @@
         isEnabled: Boolean,
         ringerMode: RingerMode,
     ): State {
+        val label =
+            labelsByStream[audioStream]?.let(context::getString)
+                ?: error("No label for the stream: $audioStream")
         return State(
             value = volume.toFloat(),
             valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
             icon = getIcon(ringerMode),
-            label = labelsByStream[audioStream]?.let(context::getString)
-                    ?: error("No label for the stream: $audioStream"),
+            label = label,
             disabledMessage = disabledTextByStream[audioStream]?.let(context::getString),
             isEnabled = isEnabled,
             a11yStep = volumeRange.step,
+            a11yClickDescription =
+                context.getString(
+                    if (isMuted) {
+                        R.string.volume_panel_hint_unmute
+                    } else {
+                        R.string.volume_panel_hint_mute
+                    },
+                    label,
+                ),
+            a11yStateDescription =
+                if (volume == volumeRange.first) {
+                    context.getString(
+                        if (audioStream.value in streamsAffectedByRing) {
+                            if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
+                                R.string.volume_panel_hint_vibrate
+                            } else {
+                                R.string.volume_panel_hint_muted
+                            }
+                        } else {
+                            R.string.volume_panel_hint_muted
+                        }
+                    )
+                } else {
+                    null
+                },
             audioStreamModel = this,
             isMutable = audioVolumeInteractor.isAffectedByMute(audioStream),
         )
@@ -143,27 +175,14 @@
         val isMutedOrNoVolume = isMuted || volume == minVolume
         val iconRes =
             if (isMutedOrNoVolume) {
-                when (audioStream.value) {
-                    AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_off
-                    AudioManager.STREAM_BLUETOOTH_SCO -> R.drawable.ic_volume_off
-                    AudioManager.STREAM_VOICE_CALL -> R.drawable.ic_volume_off
-                    AudioManager.STREAM_RING ->
-                        if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
-                            R.drawable.ic_volume_ringer_vibrate
-                        } else {
-                            R.drawable.ic_volume_off
-                        }
-                    AudioManager.STREAM_NOTIFICATION ->
-                        if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
-                            R.drawable.ic_volume_ringer_vibrate
-                        } else {
-                            R.drawable.ic_volume_off
-                        }
-                    AudioManager.STREAM_ALARM -> R.drawable.ic_volume_off
-                    else -> {
-                        Log.wtf(TAG, "No icon for the stream: $audioStream")
+                if (audioStream.value in streamsAffectedByRing) {
+                    if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
+                        R.drawable.ic_volume_ringer_vibrate
+                    } else {
                         R.drawable.ic_volume_off
                     }
+                } else {
+                    R.drawable.ic_volume_off
                 }
             } else {
                 iconsByStream[audioStream]
@@ -186,6 +205,8 @@
         override val disabledMessage: String?,
         override val isEnabled: Boolean,
         override val a11yStep: Int,
+        override val a11yClickDescription: String?,
+        override val a11yStateDescription: String?,
         override val isMutable: Boolean,
         val audioStreamModel: AudioStreamModel,
     ) : SliderState
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
index 956ab66..10714d1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
@@ -68,7 +68,7 @@
             icon = Icon.Resource(R.drawable.ic_cast, null),
             label = context.getString(R.string.media_device_cast),
             isEnabled = true,
-            a11yStep = 1
+            a11yStep = 1,
         )
     }
 
@@ -85,6 +85,12 @@
 
         override val isMutable: Boolean
             get() = false
+
+        override val a11yClickDescription: String?
+            get() = null
+
+        override val a11yStateDescription: String?
+            get() = null
     }
 
     @AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
index d71a9d8..c951928 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
@@ -34,6 +34,8 @@
      * enough to trigger rounding to the correct value.
      */
     val a11yStep: Int
+    val a11yClickDescription: String?
+    val a11yStateDescription: String?
     val disabledMessage: String?
     val isMutable: Boolean
 
@@ -44,6 +46,8 @@
         override val label: String = ""
         override val disabledMessage: String? = null
         override val a11yStep: Int = 0
+        override val a11yClickDescription: String? = null
+        override val a11yStateDescription: String? = null
         override val isEnabled: Boolean = true
         override val isMutable: Boolean = false
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
index 26d6a9a..4b4d69a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
@@ -31,13 +31,15 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.flow.transformLatest
 import kotlinx.coroutines.launch
@@ -53,12 +55,39 @@
 constructor(
     @VolumePanelScope private val scope: CoroutineScope,
     mediaOutputInteractor: MediaOutputInteractor,
-    private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
+    mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
     private val streamSliderViewModelFactory: AudioStreamSliderViewModel.Factory,
     private val castVolumeSliderViewModelFactory: CastVolumeSliderViewModel.Factory,
     streamsInteractor: AudioSlidersInteractor,
 ) {
 
+    private val mutableIsExpanded = MutableStateFlow<Boolean?>(null)
+    private val isPlaybackActive: Flow<Boolean?> =
+        mediaOutputInteractor.defaultActiveMediaSession
+            .filterData()
+            .flatMapLatest { session ->
+                if (session == null) {
+                    flowOf(false)
+                } else {
+                    mediaDeviceSessionInteractor.playbackState(session).map { it?.isActive == true }
+                }
+            }
+            .onEach { isPlaybackActive -> mutableIsExpanded.value = !isPlaybackActive }
+            .stateIn(scope, SharingStarted.Eagerly, null)
+    private val portraitExpandable: Flow<SlidersExpandableViewModel> =
+        isPlaybackActive
+            .filterNotNull()
+            .flatMapLatest { isActive ->
+                if (isActive) {
+                    mutableIsExpanded.filterNotNull().map { isExpanded ->
+                        SlidersExpandableViewModel.Expandable(isExpanded)
+                    }
+                } else {
+                    flowOf(SlidersExpandableViewModel.Fixed)
+                }
+            }
+            .stateIn(scope, SharingStarted.Eagerly, SlidersExpandableViewModel.Unavailable)
+
     val sliderViewModels: StateFlow<List<SliderViewModel>> =
         streamsInteractor.volumePanelSliders
             .transformLatest { sliderTypes ->
@@ -76,24 +105,16 @@
             }
             .stateIn(scope, SharingStarted.Eagerly, emptyList())
 
-    private val mutableIsExpanded = MutableSharedFlow<Boolean>()
-
-    val isExpanded: StateFlow<Boolean> =
-        merge(
-                mutableIsExpanded,
-                mediaOutputInteractor.defaultActiveMediaSession.filterData().flatMapLatest { session
-                    ->
-                    if (session == null) flowOf(true)
-                    else
-                        mediaDeviceSessionInteractor.playbackState(session).map {
-                            it?.isActive != true
-                        }
-                },
-            )
-            .stateIn(scope, SharingStarted.Eagerly, false)
+    fun isExpandable(isPortrait: Boolean): Flow<SlidersExpandableViewModel> {
+        return if (isPortrait) {
+            portraitExpandable
+        } else {
+            flowOf(SlidersExpandableViewModel.Fixed)
+        }
+    }
 
     fun onExpandedChanged(isExpanded: Boolean) {
-        scope.launch { mutableIsExpanded.emit(isExpanded) }
+        scope.launch { mutableIsExpanded.value = isExpanded }
     }
 
     private fun CoroutineScope.createSessionViewModel(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/SlidersExpandableViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/SlidersExpandableViewModel.kt
new file mode 100644
index 0000000..19b9ead
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/SlidersExpandableViewModel.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.volume.panel.component.volume.ui.viewmodel
+
+/**
+ * Models expandability state of the
+ * [com.android.systemui.volume.panel.component.volume.ui.composable.VolumeSlidersComponent].
+ */
+sealed interface SlidersExpandableViewModel {
+
+    /** [SlidersExpandableViewModel] is not loaded. */
+    data object Unavailable : SlidersExpandableViewModel
+
+    data class Expandable(val isExpanded: Boolean) : SlidersExpandableViewModel
+
+    data object Fixed : SlidersExpandableViewModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 1568e8c0..2e29bbd 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -20,6 +20,7 @@
 import static android.app.WallpaperManager.FLAG_SYSTEM;
 import static android.app.WallpaperManager.SetWallpaperFlags;
 
+import static com.android.systemui.Flags.fixImageWallpaperCrashSurfaceAlreadyReleased;
 import static com.android.window.flags.Flags.offloadColorExtraction;
 
 import android.annotation.Nullable;
@@ -128,8 +129,17 @@
          * and if the count is 0, unload the bitmap
          */
         private int mBitmapUsages = 0;
+
+        /**
+         * Main lock for long operations (loading the bitmap or processing colors).
+         */
         private final Object mLock = new Object();
 
+        /**
+         * Lock for SurfaceHolder operations. Should only be acquired after the main lock.
+         */
+        private final Object mSurfaceLock = new Object();
+
         CanvasEngine() {
             super();
             setFixedSizeAllowed(true);
@@ -223,6 +233,12 @@
             if (DEBUG) {
                 Log.i(TAG, "onSurfaceDestroyed");
             }
+            if (fixImageWallpaperCrashSurfaceAlreadyReleased()) {
+                synchronized (mSurfaceLock) {
+                    mSurfaceHolder = null;
+                }
+                return;
+            }
             mLongExecutor.execute(this::onSurfaceDestroyedSynchronized);
         }
 
@@ -259,7 +275,7 @@
         }
 
         private void drawFrameInternal() {
-            if (mSurfaceHolder == null) {
+            if (mSurfaceHolder == null && !fixImageWallpaperCrashSurfaceAlreadyReleased()) {
                 Log.i(TAG, "attempt to draw a frame without a valid surface");
                 return;
             }
@@ -268,6 +284,19 @@
             if (!isBitmapLoaded()) {
                 loadWallpaperAndDrawFrameInternal();
             } else {
+                if (fixImageWallpaperCrashSurfaceAlreadyReleased()) {
+                    synchronized (mSurfaceLock) {
+                        if (mSurfaceHolder == null) {
+                            Log.i(TAG, "Surface released before the image could be drawn");
+                            return;
+                        }
+                        mBitmapUsages++;
+                        drawFrameOnCanvas(mBitmap);
+                        reportEngineShown(false);
+                        unloadBitmapIfNotUsedInternal();
+                        return;
+                    }
+                }
                 mBitmapUsages++;
                 drawFrameOnCanvas(mBitmap);
                 reportEngineShown(false);
@@ -328,9 +357,14 @@
                 mBitmap.recycle();
             }
             mBitmap = null;
-
-            final Surface surface = getSurfaceHolder().getSurface();
-            surface.hwuiDestroy();
+            if (fixImageWallpaperCrashSurfaceAlreadyReleased()) {
+                synchronized (mSurfaceLock) {
+                    if (mSurfaceHolder != null) mSurfaceHolder.getSurface().hwuiDestroy();
+                }
+            } else {
+                final Surface surface = getSurfaceHolder().getSurface();
+                surface.hwuiDestroy();
+            }
             mWallpaperManager.forgetLoadedWallpaper();
             Trace.endSection();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 263ddc1..e073f7c 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -21,6 +21,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
@@ -97,7 +98,7 @@
         CoreStartable,
         CommandQueue.Callbacks {
     private static final String TAG = WMShell.class.getName();
-    private static final int INVALID_SYSUI_STATE_MASK =
+    private static final long INVALID_SYSUI_STATE_MASK =
             SYSUI_STATE_DIALOG_SHOWING
                     | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
                     | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
@@ -273,6 +274,13 @@
                 splitScreen.setSplitscreenFocus(leftOrTop);
             }
         });
+        splitScreen.registerSplitAnimationListener(new SplitScreen.SplitInvocationListener() {
+            @Override
+            public void onSplitAnimationInvoked(boolean animationRunning) {
+                mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION, animationRunning)
+                        .commitUpdate(mDisplayTracker.getDefaultDisplayId());
+            }
+        }, mSysUiMainExecutor);
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index e72027a..5702a8c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -21,14 +21,18 @@
 import android.view.ViewTreeObserver
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.log.core.LogLevel
@@ -68,8 +72,9 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
+import com.android.systemui.Flags as AConfigFlags
+import org.mockito.Mockito.`when` as whenever
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
@@ -319,26 +324,16 @@
     fun listenForDozeAmountTransition_updatesClockDozeAmount() =
         runBlocking(IMMEDIATE) {
             val transitionStep = MutableStateFlow(TransitionStep())
-            whenever(
-                    keyguardTransitionInteractor.transition(
-                        KeyguardState.LOCKSCREEN,
-                        KeyguardState.AOD
-                    )
-                )
+            whenever(keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, AOD)))
                 .thenReturn(transitionStep)
-            whenever(
-                    keyguardTransitionInteractor.transition(
-                        KeyguardState.AOD,
-                        KeyguardState.LOCKSCREEN
-                    )
-                )
+            whenever(keyguardTransitionInteractor.transition(Edge.create(AOD, LOCKSCREEN)))
                 .thenReturn(transitionStep)
 
             val job = underTest.listenForDozeAmountTransition(this)
             transitionStep.value =
                 TransitionStep(
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.AOD,
+                    from = LOCKSCREEN,
+                    to = AOD,
                     value = 0.4f,
                     transitionState = TransitionState.RUNNING,
                 )
@@ -353,14 +348,14 @@
     fun listenForTransitionToAodFromGone_updatesClockDozeAmountToOne() =
         runBlocking(IMMEDIATE) {
             val transitionStep = MutableStateFlow(TransitionStep())
-            whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD))
+            whenever(keyguardTransitionInteractor.transitionStepsToState(AOD))
                 .thenReturn(transitionStep)
 
             val job = underTest.listenForAnyStateToAodTransition(this)
             transitionStep.value =
                 TransitionStep(
-                    from = KeyguardState.GONE,
-                    to = KeyguardState.AOD,
+                    from = GONE,
+                    to = AOD,
                     transitionState = TransitionState.STARTED,
                 )
             yield()
@@ -371,19 +366,19 @@
         }
 
     @Test
-    fun listenForTransitionToLSFromOccluded_updatesClockDozeAmountToOne() =
+    fun listenForTransitionToLSFromOccluded_updatesClockDozeAmountToZero() =
         runBlocking(IMMEDIATE) {
             val transitionStep = MutableStateFlow(TransitionStep())
-            whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.LOCKSCREEN))
-                    .thenReturn(transitionStep)
+            whenever(keyguardTransitionInteractor.transitionStepsToState(LOCKSCREEN))
+                .thenReturn(transitionStep)
 
             val job = underTest.listenForAnyStateToLockscreenTransition(this)
             transitionStep.value =
-                    TransitionStep(
-                            from = KeyguardState.OCCLUDED,
-                            to = KeyguardState.LOCKSCREEN,
-                            transitionState = TransitionState.STARTED,
-                    )
+                TransitionStep(
+                    from = OCCLUDED,
+                    to = LOCKSCREEN,
+                    transitionState = TransitionState.STARTED,
+                )
             yield()
 
             verify(animations, times(2)).doze(0f)
@@ -395,37 +390,37 @@
     fun listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmount() =
         runBlocking(IMMEDIATE) {
             val transitionStep = MutableStateFlow(TransitionStep())
-            whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD))
+            whenever(keyguardTransitionInteractor.transitionStepsToState(AOD))
                 .thenReturn(transitionStep)
 
             val job = underTest.listenForAnyStateToAodTransition(this)
             transitionStep.value =
                 TransitionStep(
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.AOD,
+                    from = LOCKSCREEN,
+                    to = AOD,
                     transitionState = TransitionState.STARTED,
                 )
             yield()
 
             verify(animations, never()).doze(1f)
 
-                job.cancel()
-            }
+            job.cancel()
+        }
 
     @Test
     fun listenForAnyStateToLockscreenTransition_neverUpdatesClockDozeAmount() =
         runBlocking(IMMEDIATE) {
             val transitionStep = MutableStateFlow(TransitionStep())
-            whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.LOCKSCREEN))
-                    .thenReturn(transitionStep)
+            whenever(keyguardTransitionInteractor.transitionStepsToState(LOCKSCREEN))
+                .thenReturn(transitionStep)
 
             val job = underTest.listenForAnyStateToLockscreenTransition(this)
             transitionStep.value =
-                    TransitionStep(
-                            from = KeyguardState.AOD,
-                            to = KeyguardState.LOCKSCREEN,
-                            transitionState = TransitionState.STARTED,
-                    )
+                TransitionStep(
+                    from = AOD,
+                    to = LOCKSCREEN,
+                    transitionState = TransitionState.STARTED,
+                )
             yield()
 
             verify(animations, never()).doze(0f)
@@ -434,6 +429,27 @@
         }
 
     @Test
+    fun listenForAnyStateToDozingTransition_UpdatesClockDozeAmountToOne() =
+        runBlocking(IMMEDIATE) {
+            val transitionStep = MutableStateFlow(TransitionStep())
+            whenever(keyguardTransitionInteractor.transitionStepsToState(DOZING))
+                .thenReturn(transitionStep)
+
+            val job = underTest.listenForAnyStateToDozingTransition(this)
+            transitionStep.value =
+                TransitionStep(
+                    from = LOCKSCREEN,
+                    to = DOZING,
+                    transitionState = TransitionState.STARTED,
+                )
+            yield()
+
+            verify(animations, times(2)).doze(1f)
+
+            job.cancel()
+        }
+
+    @Test
     fun unregisterListeners_validate() =
         runBlocking(IMMEDIATE) {
             underTest.unregisterListeners()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 51ceda3..f9fe5e7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.when;
 
 import android.os.SystemClock;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.KeyEvent;
@@ -125,8 +126,8 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
     public void withFeatureFlagOn_oldMessage_isHidden() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES);
         KeyguardAbsKeyInputViewController underTest = createTestObject();
 
         underTest.init();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index 25e5470..3164f8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.accessibility;
 
+import static com.android.systemui.accessibility.Magnification.DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -23,11 +25,17 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.hardware.display.DisplayManager;
 import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -39,6 +47,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
@@ -47,6 +56,7 @@
 import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -58,9 +68,12 @@
  */
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class IMagnificationConnectionTest extends SysuiTestCase {
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
     @Mock
     private AccessibilityManager mAccessibilityManager;
@@ -90,6 +103,7 @@
     private IMagnificationConnection mIMagnificationConnection;
     private Magnification mMagnification;
     private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
+    private TestableLooper mTestableLooper;
 
     @Before
     public void setUp() throws Exception {
@@ -100,8 +114,10 @@
             return null;
         }).when(mAccessibilityManager).setMagnificationConnection(
                 any(IMagnificationConnection.class));
+        mTestableLooper = TestableLooper.get(this);
+        assertNotNull(mTestableLooper);
         mMagnification = new Magnification(getContext(),
-                getContext().getMainThreadHandler(), getContext().getMainExecutor(), mCommandQueue,
+                mTestableLooper.getLooper(), getContext().getMainExecutor(), mCommandQueue,
                 mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
                 mDisplayTracker, getContext().getSystemService(DisplayManager.class), mA11yLogger);
         mMagnification.mWindowMagnificationControllerSupplier =
@@ -122,7 +138,7 @@
     public void enableWindowMagnification_passThrough() throws RemoteException {
         mIMagnificationConnection.enableWindowMagnification(TEST_DISPLAY, 3.0f, Float.NaN,
                 Float.NaN, 0f, 0f, mAnimationCallback);
-        waitForIdleSync();
+        processAllPendingMessages();
 
         verify(mWindowMagnificationController).enableWindowMagnification(eq(3.0f),
                 eq(Float.NaN), eq(Float.NaN), eq(0f), eq(0f), eq(mAnimationCallback));
@@ -131,7 +147,7 @@
     @Test
     public void onFullscreenMagnificationActivationChanged_passThrough() throws RemoteException {
         mIMagnificationConnection.onFullscreenMagnificationActivationChanged(TEST_DISPLAY, true);
-        waitForIdleSync();
+        processAllPendingMessages();
 
         verify(mFullscreenMagnificationController)
                 .onFullscreenMagnificationActivationChanged(eq(true));
@@ -141,7 +157,7 @@
     public void disableWindowMagnification_deleteWindowMagnification() throws RemoteException {
         mIMagnificationConnection.disableWindowMagnification(TEST_DISPLAY,
                 mAnimationCallback);
-        waitForIdleSync();
+        processAllPendingMessages();
 
         verify(mWindowMagnificationController).deleteWindowMagnification(
                 mAnimationCallback);
@@ -150,7 +166,7 @@
     @Test
     public void setScaleForWindowMagnification() throws RemoteException {
         mIMagnificationConnection.setScaleForWindowMagnification(TEST_DISPLAY, 3.0f);
-        waitForIdleSync();
+        processAllPendingMessages();
 
         verify(mWindowMagnificationController).setScale(3.0f);
     }
@@ -158,7 +174,7 @@
     @Test
     public void moveWindowMagnifier() throws RemoteException {
         mIMagnificationConnection.moveWindowMagnifier(TEST_DISPLAY, 100f, 200f);
-        waitForIdleSync();
+        processAllPendingMessages();
 
         verify(mWindowMagnificationController).moveWindowMagnifier(100f, 200f);
     }
@@ -167,37 +183,102 @@
     public void moveWindowMagnifierToPosition() throws RemoteException {
         mIMagnificationConnection.moveWindowMagnifierToPosition(TEST_DISPLAY,
                 100f, 200f, mAnimationCallback);
-        waitForIdleSync();
+        processAllPendingMessages();
 
         verify(mWindowMagnificationController).moveWindowMagnifierToPosition(
                 eq(100f), eq(200f), any(IRemoteMagnificationAnimationCallback.class));
     }
 
     @Test
-    public void showMagnificationButton() throws RemoteException {
+    @RequiresFlagsDisabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON)
+    public void showMagnificationButton_flagOff_directlyShowButton() throws RemoteException {
         // magnification settings panel should not be showing
         assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY));
 
         mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
-        waitForIdleSync();
+        processAllPendingMessages();
 
         verify(mModeSwitchesController).showButton(TEST_DISPLAY,
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON)
+    public void showMagnificationButton_flagOn_delayedShowButton() throws RemoteException {
+        // magnification settings panel should not be showing
+        assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY));
+
+        mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+        // This processAllPendingMessages lets the IMagnificationConnection to delegate the
+        // showMagnificationButton request to Magnification.
+        processAllPendingMessages();
+
+        // The delayed message would be processed after DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS.
+        // So call this processAllPendingMessages with a timeout to verify the showButton
+        // will be called.
+        int timeout = DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS + 100;
+        processAllPendingMessages(timeout);
+        verify(mModeSwitchesController).showButton(TEST_DISPLAY,
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+    }
+
+    @Test
+    public void showMagnificationButton_settingsPanelShowing_doNotShowButton()
+            throws RemoteException {
+        when(mMagnificationSettingsController.isMagnificationSettingsShowing()).thenReturn(true);
+
+        mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+        // This processAllPendingMessages lets the IMagnificationConnection to delegate the
+        // showMagnificationButton request to Magnification.
+        processAllPendingMessages();
+
+        // If the flag is on, the isMagnificationSettingsShowing will be checked after timeout, so
+        // process all message after a timeout here to verify the showButton will not be called.
+        int timeout = Flags.delayShowMagnificationButton()
+                ? DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS + 100
+                : 0;
+        processAllPendingMessages(timeout);
+        verify(mModeSwitchesController, never()).showButton(TEST_DISPLAY,
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+    }
+
+    @Test
     public void removeMagnificationButton() throws RemoteException {
         mIMagnificationConnection.removeMagnificationButton(TEST_DISPLAY);
-        waitForIdleSync();
+        processAllPendingMessages();
 
         verify(mModeSwitchesController).removeButton(TEST_DISPLAY);
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON)
+    public void removeMagnificationButton_delayingShowButton_doNotShowButtonAfterTimeout()
+            throws RemoteException {
+        // magnification settings panel should not be showing
+        assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY));
+
+        mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+        mIMagnificationConnection.removeMagnificationButton(TEST_DISPLAY);
+        // This processAllPendingMessages lets the IMagnificationConnection to delegate the
+        // requests to Magnification.
+        processAllPendingMessages();
+
+        // Call this processAllPendingMessages with a timeout to ensure the delayed show button
+        // message should be removed and thus the showButton will not be called after timeout.
+        int timeout = DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS + 100;
+        processAllPendingMessages(/* timeForwardMs= */ timeout);
+        verify(mModeSwitchesController, never()).showButton(TEST_DISPLAY,
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+    }
+
+    @Test
     public void removeMagnificationSettingsPanel() throws RemoteException {
         mIMagnificationConnection.removeMagnificationSettingsPanel(TEST_DISPLAY);
-        waitForIdleSync();
+        processAllPendingMessages();
 
         verify(mMagnificationSettingsController).closeMagnificationSettings();
     }
@@ -208,7 +289,7 @@
         final float testScale = 3.0f;
         mIMagnificationConnection.onUserMagnificationScaleChanged(
                 testUserId, TEST_DISPLAY, testScale);
-        waitForIdleSync();
+        processAllPendingMessages();
 
         assertTrue(mMagnification.mUsersScales.contains(testUserId));
         assertEquals(mMagnification.mUsersScales.get(testUserId).get(TEST_DISPLAY),
@@ -216,6 +297,17 @@
         verify(mMagnificationSettingsController).setMagnificationScale(eq(testScale));
     }
 
+    private void processAllPendingMessages() {
+        processAllPendingMessages(/* timeForwardMs=*/ 0);
+    }
+
+    private void processAllPendingMessages(int timeForwardMs) {
+        if (timeForwardMs > 0) {
+            mTestableLooper.moveTimeForward(timeForwardMs);
+        }
+        mTestableLooper.processAllMessages();
+    }
+
     private class FakeWindowMagnificationControllerSupplier extends
             DisplayIdIndexSupplier<WindowMagnificationController> {
 
@@ -229,7 +321,6 @@
         }
     }
 
-
     private class FakeFullscreenMagnificationControllerSupplier extends
             DisplayIdIndexSupplier<FullscreenMagnificationController> {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index 6dc5b72..bbdd805 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.doAnswer;
@@ -104,7 +105,7 @@
         }).when(mAccessibilityManager).setMagnificationConnection(
                 any(IMagnificationConnection.class));
 
-        when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
+        when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
 
         doAnswer(invocation -> {
             mMagnification.mMagnificationSettingsControllerCallback
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
similarity index 77%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
index 516b665..93c0eea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
@@ -39,9 +39,9 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class WindowMagnificationSizePrefsTest extends SysuiTestCase {
+public class WindowMagnificationFrameSizePrefsTest extends SysuiTestCase {
 
-    WindowMagnificationSizePrefs mWindowMagnificationSizePrefs;
+    WindowMagnificationFrameSizePrefs mWindowMagnificationFrameSizePrefs;
     FakeSharedPreferences mSharedPreferences;
 
     @Before
@@ -51,24 +51,24 @@
         when(mContext.getSharedPreferences(
                 eq("window_magnification_preferences"), anyInt()))
                 .thenReturn(mSharedPreferences);
-        mWindowMagnificationSizePrefs = new WindowMagnificationSizePrefs(mContext);
+        mWindowMagnificationFrameSizePrefs = new WindowMagnificationFrameSizePrefs(mContext);
     }
 
     @Test
     public void saveSizeForCurrentDensity_getExpectedSize() {
         Size testSize = new Size(500, 500);
-        mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(testSize);
+        mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity(testSize);
 
-        assertThat(mWindowMagnificationSizePrefs.getSizeForCurrentDensity())
+        assertThat(mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity())
                 .isEqualTo(testSize);
     }
 
     @Test
     public void saveSizeForCurrentDensity_containsPreferenceForCurrentDensity() {
         Size testSize = new Size(500, 500);
-        mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(testSize);
+        mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity(testSize);
 
-        assertThat(mWindowMagnificationSizePrefs.isPreferenceSavedForCurrentDensity())
+        assertThat(mWindowMagnificationFrameSizePrefs.isPreferenceSavedForCurrentDensity())
                 .isTrue();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index e0df1e0..2d5e3a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -26,7 +26,6 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.graphics.PointF;
-import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
@@ -40,7 +39,6 @@
 import androidx.dynamicanimation.animation.SpringForce;
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.Flags;
 import com.android.systemui.Prefs;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.accessibility.utils.TestUtils;
@@ -230,7 +228,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK)
     public void tuck_animates() {
         mMenuAnimationController.cancelAnimations();
         mMenuAnimationController.moveToEdgeAndHide();
@@ -239,7 +236,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK)
     public void untuck_animates() {
         mMenuAnimationController.cancelAnimations();
         mMenuAnimationController.moveOutEdgeAndShow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
index bf6ca06..e371b39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
@@ -46,12 +46,12 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.Mock
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
 
 private const val ON: Int = 1
 private const val OFF: Int = 0
@@ -90,7 +90,7 @@
         secureSettings = FakeSettings()
         systemClock = FakeSystemClock()
         backgroundDelayableExecutor = FakeExecutor(systemClock)
-        whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
+        whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState)
 
         fontScalingDialogDelegate =
             spy(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index ebb6b48..8895a5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -118,7 +119,7 @@
         when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
         when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
         when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
-        when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
+        when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
         when(mCachedDevice.getAddress()).thenReturn(DEVICE_ADDRESS);
         when(mHearingDeviceItem.getCachedBluetoothDevice()).thenReturn(mCachedDevice);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
index 41974f4..fd37cad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
@@ -8,6 +8,7 @@
 import android.graphics.Point
 import android.graphics.Rect
 import android.os.Looper
+import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.IRemoteAnimationFinishedCallback
@@ -17,15 +18,20 @@
 import android.view.ViewGroup
 import android.widget.FrameLayout
 import android.widget.LinearLayout
+import android.window.RemoteTransition
+import android.window.TransitionFilter
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.Flags
 import com.android.systemui.util.mockito.any
+import com.android.wm.shell.shared.ShellTransitions
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertNotNull
 import junit.framework.Assert.assertNull
 import junit.framework.Assert.assertTrue
 import junit.framework.AssertionFailedError
 import kotlin.concurrent.thread
+import kotlin.test.assertEquals
 import org.junit.After
 import org.junit.Assert.assertThrows
 import org.junit.Before
@@ -46,7 +52,9 @@
 @RunWithLooper
 class ActivityTransitionAnimatorTest : SysuiTestCase() {
     private val transitionContainer = LinearLayout(mContext)
-    private val testTransitionAnimator = fakeTransitionAnimator()
+    private val mainExecutor = context.mainExecutor
+    private val testTransitionAnimator = fakeTransitionAnimator(mainExecutor)
+    private val testShellTransitions = FakeShellTransitions()
     @Mock lateinit var callback: ActivityTransitionAnimator.Callback
     @Mock lateinit var listener: ActivityTransitionAnimator.Listener
     @Spy private val controller = TestTransitionAnimatorController(transitionContainer)
@@ -54,14 +62,19 @@
 
     private lateinit var activityTransitionAnimator: ActivityTransitionAnimator
     @get:Rule val rule = MockitoJUnit.rule()
+    @get:Rule val setFlagsRule = SetFlagsRule()
 
     @Before
     fun setup() {
         activityTransitionAnimator =
             ActivityTransitionAnimator(
+                mainExecutor,
+                ActivityTransitionAnimator.TransitionRegister.fromShellTransitions(
+                    testShellTransitions
+                ),
                 testTransitionAnimator,
                 testTransitionAnimator,
-                disableWmTimeout = true
+                disableWmTimeout = true,
             )
         activityTransitionAnimator.callback = callback
         activityTransitionAnimator.addListener(listener)
@@ -162,6 +175,34 @@
     }
 
     @Test
+    fun registersReturnIffCookieIsPresent() {
+        setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)
+        `when`(callback.isOnKeyguard()).thenReturn(false)
+
+        startIntentWithAnimation(activityTransitionAnimator, controller) { _ ->
+            ActivityManager.START_DELIVERED_TO_TOP
+        }
+
+        waitForIdleSync()
+        assertTrue(testShellTransitions.remotes.isEmpty())
+        assertTrue(testShellTransitions.remotesForTakeover.isEmpty())
+
+        val controller =
+            object : DelegateTransitionAnimatorController(controller) {
+                override val transitionCookie
+                    get() = ActivityTransitionAnimator.TransitionCookie("testCookie")
+            }
+
+        startIntentWithAnimation(activityTransitionAnimator, controller) { _ ->
+            ActivityManager.START_DELIVERED_TO_TOP
+        }
+
+        waitForIdleSync()
+        assertEquals(1, testShellTransitions.remotes.size)
+        assertTrue(testShellTransitions.remotesForTakeover.isEmpty())
+    }
+
+    @Test
     fun doesNotStartIfAnimationIsCancelled() {
         val runner = activityTransitionAnimator.createRunner(controller)
         runner.onAnimationCancelled()
@@ -241,6 +282,35 @@
 }
 
 /**
+ * A fake implementation of [ShellTransitions] which saves filter-transition pairs locally and
+ * allows inspection.
+ */
+private class FakeShellTransitions : ShellTransitions {
+    val remotes = mutableMapOf<TransitionFilter, RemoteTransition>()
+    val remotesForTakeover = mutableMapOf<TransitionFilter, RemoteTransition>()
+
+    override fun registerRemote(filter: TransitionFilter, remoteTransition: RemoteTransition) {
+        remotes[filter] = remoteTransition
+    }
+
+    override fun registerRemoteForTakeover(
+        filter: TransitionFilter,
+        remoteTransition: RemoteTransition
+    ) {
+        remotesForTakeover[filter] = remoteTransition
+    }
+
+    override fun unregisterRemote(remoteTransition: RemoteTransition) {
+        while (remotes.containsValue(remoteTransition)) {
+            remotes.values.remove(remoteTransition)
+        }
+        while (remotesForTakeover.containsValue(remoteTransition)) {
+            remotesForTakeover.values.remove(remoteTransition)
+        }
+    }
+}
+
+/**
  * A simple implementation of [ActivityTransitionAnimator.Controller] which throws if it is called
  * outside of the main thread.
  */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt
index d84a578..e14762cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt
@@ -156,6 +156,7 @@
     fun testActivityLaunchWhenLockedWithoutAlternateAuth() {
         val dialogTransitionAnimator =
                 fakeDialogTransitionAnimator(
+                        mainExecutor = mContext.mainExecutor,
                         isUnlocked = false,
                         isShowingAlternateAuthOnUnlock = false,
                         interactionJankMonitor = kosmos.interactionJankMonitor)
@@ -166,6 +167,7 @@
     @Test
     fun testActivityLaunchWhenLockedWithAlternateAuth() {
         val dialogTransitionAnimator = fakeDialogTransitionAnimator(
+                mainExecutor = mContext.mainExecutor,
                 isUnlocked = false,
                 isShowingAlternateAuthOnUnlock = true,
                 interactionJankMonitor = kosmos.interactionJankMonitor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
index b31fe21..42fcd54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
@@ -16,12 +16,16 @@
 
 package com.android.systemui.animation
 
+import android.os.HandlerThread
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import android.view.View
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
+import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.view.LaunchableFrameLayout
+import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertThrows
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -30,6 +34,13 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class GhostedViewTransitionAnimatorControllerTest : SysuiTestCase() {
+    companion object {
+        private const val LAUNCH_CUJ = 0
+        private const val RETURN_CUJ = 1
+    }
+
+    private val interactionJankMonitor = FakeInteractionJankMonitor()
+
     @Test
     fun animatingOrphanViewDoesNotCrash() {
         val state = TransitionAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
@@ -47,4 +58,63 @@
             GhostedViewTransitionAnimatorController(FrameLayout(mContext))
         }
     }
+
+    @Test
+    fun cujsAreLoggedCorrectly() {
+        val parent = FrameLayout(mContext)
+
+        val launchView = LaunchableFrameLayout(mContext)
+        parent.addView((launchView))
+        val launchController =
+            GhostedViewTransitionAnimatorController(
+                    launchView,
+                launchCujType = LAUNCH_CUJ,
+                returnCujType = RETURN_CUJ,
+                interactionJankMonitor = interactionJankMonitor
+            )
+        launchController.onTransitionAnimationStart(isExpandingFullyAbove = true)
+        assertThat(interactionJankMonitor.ongoing).containsExactly(LAUNCH_CUJ)
+        launchController.onTransitionAnimationEnd(isExpandingFullyAbove = true)
+        assertThat(interactionJankMonitor.ongoing).isEmpty()
+        assertThat(interactionJankMonitor.finished).containsExactly(LAUNCH_CUJ)
+
+        val returnView = LaunchableFrameLayout(mContext)
+        parent.addView((returnView))
+        val returnController =
+            object : GhostedViewTransitionAnimatorController(
+                returnView,
+                launchCujType = LAUNCH_CUJ,
+                returnCujType = RETURN_CUJ,
+                interactionJankMonitor = interactionJankMonitor
+            ) {
+                override val isLaunching = false
+            }
+        returnController.onTransitionAnimationStart(isExpandingFullyAbove = true)
+        assertThat(interactionJankMonitor.ongoing).containsExactly(RETURN_CUJ)
+        returnController.onTransitionAnimationEnd(isExpandingFullyAbove = true)
+        assertThat(interactionJankMonitor.ongoing).isEmpty()
+        assertThat(interactionJankMonitor.finished).containsExactly(LAUNCH_CUJ, RETURN_CUJ)
+    }
+
+    /**
+     * A fake implementation of [InteractionJankMonitor] which stores ongoing and finished CUJs and
+     * allows inspection.
+     */
+    private class FakeInteractionJankMonitor : InteractionJankMonitor(
+        HandlerThread("testThread")
+    ) {
+        val ongoing: MutableSet<Int> = mutableSetOf()
+        val finished: MutableSet<Int> = mutableSetOf()
+
+        override fun begin(v: View?, cujType: Int): Boolean {
+            ongoing.add(cujType)
+            return true
+        }
+
+        override fun end(cujType: Int): Boolean {
+            ongoing.remove(cujType)
+            finished.add(cujType)
+            return true
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
index e64df90..259ece9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
@@ -25,6 +25,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.activity.EmptyTestActivity
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.Kosmos
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -58,9 +60,11 @@
             )
     }
 
+    private val kosmos = Kosmos()
     private val pathManager = GoldenPathManager(context, GOLDENS_PATH, pathConfig = PathConfig())
     private val transitionAnimator =
         TransitionAnimator(
+            kosmos.fakeExecutor,
             ActivityTransitionAnimator.TIMINGS,
             ActivityTransitionAnimator.INTERPOLATORS
         )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index de3b741..e81369d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -43,6 +43,7 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.widget.LockPatternUtils
+import com.android.launcher3.icons.IconProvider
 import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
@@ -150,6 +151,7 @@
     private lateinit var displayStateInteractor: DisplayStateInteractor
     private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
     private lateinit var biometricStatusInteractor: BiometricStatusInteractor
+    private lateinit var iconProvider: IconProvider
 
     private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
     private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
@@ -178,6 +180,7 @@
         biometricStatusInteractor =
                 BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository,
                     fingerprintRepository)
+        iconProvider = IconProvider(context)
         // Set up default logo icon
         whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon)
         context.setMockPackageManager(packageManager)
@@ -649,14 +652,15 @@
         lockPatternUtils,
         interactionJankMonitor,
         { promptSelectorInteractor },
-        { bpCredentialInteractor },
         PromptViewModel(
             displayStateInteractor,
             promptSelectorInteractor,
             context,
             udfpsOverlayInteractor,
             biometricStatusInteractor,
-            udfpsUtils
+            udfpsUtils,
+            iconProvider,
+            activityTaskManager
         ),
         { credentialViewModel },
         Handler(TestableLooper.get(this).looper),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index 67ca9a4..0231486 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -16,80 +16,73 @@
 
 package com.android.systemui.biometrics
 
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.flags.Flags
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.user.domain.UserDomainLayerModule
-import dagger.BindsInstance
-import dagger.Component
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shadeTestUtil
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.verifyZeroInteractions
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class AuthDialogPanelInteractionDetectorTest(flags: FlagsParameterization?) : SysuiTestCase() {
 
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                UserDomainLayerModule::class,
-                BiometricsDomainLayerModule::class,
-            ]
-    )
-    interface TestComponent : SysUITestComponent<AuthDialogPanelInteractionDetector> {
-
-        val shadeRepository: FakeShadeRepository
-
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-                featureFlags: FakeFeatureFlagsClassicModule,
-            ): TestComponent
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
         }
     }
 
-    private val testComponent: TestComponent =
-        DaggerAuthDialogPanelInteractionDetectorTest_TestComponent.factory()
-            .create(
-                test = this,
-                featureFlags =
-                    FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) },
-            )
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags!!)
+    }
 
-    private val detector: AuthDialogPanelInteractionDetector = testComponent.underTest
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
 
     @Mock private lateinit var action: Runnable
 
+    lateinit var detector: AuthDialogPanelInteractionDetector
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        detector =
+            AuthDialogPanelInteractionDetector(
+                kosmos.applicationCoroutineScope,
+                { kosmos.shadeInteractor },
+            )
     }
 
     @Test
     fun enableDetector_expand_shouldRunAction() =
-        testComponent.runTest {
+        testScope.runTest {
             // GIVEN shade is closed and detector is enabled
-            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeTestUtil.setShadeExpansion(0f)
             detector.enable(action)
             runCurrent()
 
             // WHEN shade expands
-            shadeRepository.setLegacyShadeTracking(true)
-            shadeRepository.setLegacyShadeExpansion(.5f)
+            shadeTestUtil.setTracking(true)
+            shadeTestUtil.setShadeExpansion(.5f)
             runCurrent()
 
             // THEN action was run
@@ -98,9 +91,9 @@
 
     @Test
     fun enableDetector_isUserInteractingTrue_shouldNotPostRunnable() =
-        testComponent.runTest {
+        testScope.runTest {
             // GIVEN isInteracting starts true
-            shadeRepository.setLegacyShadeTracking(true)
+            shadeTestUtil.setTracking(true)
             runCurrent()
             detector.enable(action)
 
@@ -110,33 +103,34 @@
 
     @Test
     fun enableDetector_shadeExpandImmediate_shouldNotPostRunnable() =
-        testComponent.runTest {
+        testScope.runTest {
             // GIVEN shade is closed and detector is enabled
-            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeTestUtil.setShadeExpansion(0f)
             detector.enable(action)
             runCurrent()
 
             // WHEN shade expands fully instantly
-            shadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             runCurrent()
 
             // THEN action not run
             verifyZeroInteractions(action)
+            detector.disable()
         }
 
     @Test
     fun disableDetector_shouldNotPostRunnable() =
-        testComponent.runTest {
+        testScope.runTest {
             // GIVEN shade is closed and detector is enabled
-            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeTestUtil.setShadeExpansion(0f)
             detector.enable(action)
             runCurrent()
 
             // WHEN detector is disabled and shade opens
             detector.disable()
             runCurrent()
-            shadeRepository.setLegacyShadeTracking(true)
-            shadeRepository.setLegacyShadeExpansion(.5f)
+            shadeTestUtil.setTracking(true)
+            shadeTestUtil.setShadeExpansion(.5f)
             runCurrent()
 
             // THEN action not run
@@ -145,17 +139,18 @@
 
     @Test
     fun enableDetector_beginCollapse_shouldNotPostRunnable() =
-        testComponent.runTest {
+        testScope.runTest {
             // GIVEN shade is open and detector is enabled
-            shadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setShadeExpansion(1f)
             detector.enable(action)
             runCurrent()
 
             // WHEN shade begins to collapse
-            shadeRepository.setLegacyShadeExpansion(.5f)
+            shadeTestUtil.programmaticCollapseShade()
             runCurrent()
 
             // THEN action not run
             verifyZeroInteractions(action)
+            detector.disable()
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
index df0e5a7..5e4272f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
@@ -16,13 +16,8 @@
 
 package com.android.systemui.biometrics.data.repository
 
-import android.hardware.biometrics.BiometricManager
-import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
-import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton
 import android.hardware.biometrics.PromptInfo
-import android.hardware.biometrics.PromptVerticalListContentView
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.shared.model.PromptKind
@@ -139,83 +134,6 @@
         }
 
     @Test
-    fun showBpWithoutIconForCredential_withVerticalListContentView() =
-        testScope.runTest {
-            mSetFlagsRule.enableFlags(Flags.FLAG_CONSTRAINT_BP)
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            for (case in
-                listOf(
-                    PromptKind.Biometric(),
-                    PromptKind.Pin,
-                    PromptKind.Password,
-                    PromptKind.Pattern
-                )) {
-                val hasCredentialViewShown = case !is PromptKind.Biometric
-                val promptInfo =
-                    PromptInfo().apply {
-                        authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
-                        contentView = PromptVerticalListContentView.Builder().build()
-                    }
-                repository.setPrompt(promptInfo, USER_ID, CHALLENGE, case, OP_PACKAGE_NAME)
-                repository.setShouldShowBpWithoutIconForCredential(promptInfo)
-
-                assertThat(repository.showBpWithoutIconForCredential.value)
-                    .isEqualTo(!hasCredentialViewShown)
-            }
-        }
-
-    @Test
-    fun showBpWithoutIconForCredential_withContentViewWithMoreOptionsButton() =
-        testScope.runTest {
-            mSetFlagsRule.enableFlags(Flags.FLAG_CONSTRAINT_BP)
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            val promptInfo =
-                PromptInfo().apply {
-                    authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
-                    contentView =
-                        PromptContentViewWithMoreOptionsButton.Builder()
-                            .setMoreOptionsButtonListener(fakeExecutor) { _, _ -> }
-                            .build()
-                }
-            for (case in
-                listOf(
-                    PromptKind.Biometric(),
-                    PromptKind.Pin,
-                    PromptKind.Password,
-                    PromptKind.Pattern
-                )) {
-                repository.setPrompt(promptInfo, USER_ID, CHALLENGE, case, OP_PACKAGE_NAME)
-                repository.setShouldShowBpWithoutIconForCredential(promptInfo)
-
-                assertThat(repository.showBpWithoutIconForCredential.value).isFalse()
-            }
-        }
-
-    @Test
-    fun showBpWithoutIconForCredential_withDescription() =
-        testScope.runTest {
-            mSetFlagsRule.enableFlags(Flags.FLAG_CONSTRAINT_BP)
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            for (case in
-                listOf(
-                    PromptKind.Biometric(),
-                    PromptKind.Pin,
-                    PromptKind.Password,
-                    PromptKind.Pattern
-                )) {
-                val promptInfo =
-                    PromptInfo().apply {
-                        authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
-                        description = "description"
-                    }
-                repository.setPrompt(promptInfo, USER_ID, CHALLENGE, case, OP_PACKAGE_NAME)
-                repository.setShouldShowBpWithoutIconForCredential(promptInfo)
-
-                assertThat(repository.showBpWithoutIconForCredential.value).isFalse()
-            }
-        }
-
-    @Test
     fun setsAndUnsetsPrompt() =
         testScope.runTest {
             val kind = PromptKind.Pin
@@ -223,7 +141,7 @@
 
             repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind, OP_PACKAGE_NAME)
 
-            assertThat(repository.kind.value).isEqualTo(kind)
+            assertThat(repository.promptKind.value).isEqualTo(kind)
             assertThat(repository.userId.value).isEqualTo(USER_ID)
             assertThat(repository.challenge.value).isEqualTo(CHALLENGE)
             assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
index 2172bc5..8695c01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
@@ -5,12 +5,12 @@
 import android.hardware.biometrics.PromptVerticalListContentView
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.data.repository.FakePromptRepository
 import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
 import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
 import com.android.systemui.biometrics.promptInfo
 import com.android.systemui.biometrics.shared.model.BiometricUserInfo
+import com.android.systemui.biometrics.shared.model.PromptKind
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
@@ -110,7 +110,7 @@
                     it.description = description
                     it.subtitle = subtitle
                 },
-                kind = Utils.CREDENTIAL_PIN,
+                kind = PromptKind.Pin,
                 userId = USER_ID,
                 challenge = OPERATION_ID,
                 opPackageName = OP_PACKAGE_NAME
@@ -135,7 +135,7 @@
                     it.subtitle = subtitle
                     it.contentView = contentView
                 },
-                kind = Utils.CREDENTIAL_PIN,
+                kind = PromptKind.Pin,
                 userId = USER_ID,
                 challenge = OPERATION_ID,
                 opPackageName = OP_PACKAGE_NAME
@@ -163,7 +163,7 @@
                     it.subtitle = subtitle
                     it.contentView = contentView
                 },
-                kind = Utils.CREDENTIAL_PIN,
+                kind = PromptKind.Pin,
                 userId = USER_ID,
                 challenge = OPERATION_ID,
                 opPackageName = OP_PACKAGE_NAME
@@ -171,13 +171,13 @@
             assertThat(showTitleOnly).isFalse()
         }
 
-    @Test fun usePinCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PIN)
+    @Test fun usePinCredentialForPrompt() = useCredentialForPrompt(PromptKind.Pin)
 
-    @Test fun usePasswordCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PASSWORD)
+    @Test fun usePasswordCredentialForPrompt() = useCredentialForPrompt(PromptKind.Password)
 
-    @Test fun usePatternCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PATTERN)
+    @Test fun usePatternCredentialForPrompt() = useCredentialForPrompt(PromptKind.Pattern)
 
-    private fun useCredentialForPrompt(kind: Int) =
+    private fun useCredentialForPrompt(kind: PromptKind) =
         testScope.runTest {
             val isStealth = false
             credentialInteractor.stealthMode = isStealth
@@ -211,11 +211,10 @@
             assertThat(prompt)
                 .isInstanceOf(
                     when (kind) {
-                        Utils.CREDENTIAL_PIN -> BiometricPromptRequest.Credential.Pin::class.java
-                        Utils.CREDENTIAL_PASSWORD ->
+                        PromptKind.Pin -> BiometricPromptRequest.Credential.Pin::class.java
+                        PromptKind.Password ->
                             BiometricPromptRequest.Credential.Password::class.java
-                        Utils.CREDENTIAL_PATTERN ->
-                            BiometricPromptRequest.Credential.Pattern::class.java
+                        PromptKind.Pattern -> BiometricPromptRequest.Credential.Pattern::class.java
                         else -> throw Exception("wrong kind")
                     }
                 )
@@ -341,6 +340,28 @@
 
             job.cancel()
         }
+
+    /** Update the current request to use credential-based authentication instead of biometrics. */
+    private fun PromptCredentialInteractor.useCredentialsForAuthentication(
+        promptInfo: PromptInfo,
+        kind: PromptKind,
+        userId: Int,
+        challenge: Long,
+        opPackageName: String,
+    ) {
+        biometricPromptRepository.setPrompt(
+            promptInfo,
+            userId,
+            challenge,
+            kind,
+            opPackageName,
+        )
+    }
+
+    /** Unset the current authentication request. */
+    private fun PromptCredentialInteractor.resetPrompt() {
+        biometricPromptRepository.unsetPrompt()
+    }
 }
 
 private fun pinRequest(): BiometricPromptRequest.Credential.Pin =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
index 2817780..4068404 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
@@ -17,12 +17,14 @@
 package com.android.systemui.biometrics.domain.interactor
 
 import android.app.admin.DevicePolicyManager
+import android.content.ComponentName
 import android.hardware.biometrics.BiometricManager.Authenticators
+import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton
 import android.hardware.biometrics.PromptInfo
+import android.hardware.biometrics.PromptVerticalListContentView
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.data.repository.FakePromptRepository
 import com.android.systemui.biometrics.faceSensorPropertiesInternal
@@ -30,8 +32,10 @@
 import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.PromptKind
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
@@ -44,19 +48,22 @@
 import org.mockito.Mock
 import org.mockito.junit.MockitoJUnit
 
-private const val TITLE = "hey there"
-private const val SUBTITLE = "ok"
-private const val DESCRIPTION = "football"
-private const val NEGATIVE_TEXT = "escape"
-
-private const val USER_ID = 8
-private const val CHALLENGE = 999L
-private const val OP_PACKAGE_NAME = "biometric.testapp"
-
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class PromptSelectorInteractorImplTest : SysuiTestCase() {
+    companion object {
+        private const val TITLE = "hey there"
+        private const val SUBTITLE = "ok"
+        private const val DESCRIPTION = "football"
+        private const val NEGATIVE_TEXT = "escape"
+
+        private const val USER_ID = 8
+        private const val CHALLENGE = 999L
+        private const val OP_PACKAGE_NAME = "biometric.testapp"
+        private val componentNameOverriddenForConfirmDeviceCredentialActivity =
+            ComponentName("not.com.android.settings", "testapp")
+    }
 
     @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
 
@@ -65,6 +72,7 @@
     private val testScope = TestScope()
     private val fingerprintRepository = FakeFingerprintPropertyRepository()
     private val promptRepository = FakePromptRepository()
+    private val fakeExecutor = FakeExecutor(FakeSystemClock())
 
     private lateinit var interactor: PromptSelectorInteractor
 
@@ -74,6 +82,23 @@
             PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
     }
 
+    private fun basicPromptInfo() =
+        PromptInfo().apply {
+            title = TITLE
+            subtitle = SUBTITLE
+            description = DESCRIPTION
+            negativeButtonText = NEGATIVE_TEXT
+            isConfirmationRequested = true
+            isDeviceCredentialAllowed = true
+            authenticators = Authenticators.BIOMETRIC_STRONG or Authenticators.DEVICE_CREDENTIAL
+        }
+
+    private val modalities =
+        BiometricModalities(
+            fingerprintProperties = fingerprintSensorPropertiesInternal().first(),
+            faceProperties = faceSensorPropertiesInternal().first(),
+        )
+
     @Test
     fun useBiometricsAndReset() =
         testScope.runTest { useBiometricsAndReset(allowCredentialFallback = true) }
@@ -82,16 +107,24 @@
     fun useBiometricsAndResetWithoutFallback() =
         testScope.runTest { useBiometricsAndReset(allowCredentialFallback = false) }
 
-    private fun TestScope.useBiometricsAndReset(allowCredentialFallback: Boolean) {
+    @Test
+    fun useBiometricsAndResetOnConfirmDeviceCredentialActivity() =
+        testScope.runTest {
+            useBiometricsAndReset(
+                allowCredentialFallback = true,
+                setComponentNameForConfirmDeviceCredentialActivity = true
+            )
+        }
+
+    private fun TestScope.useBiometricsAndReset(
+        allowCredentialFallback: Boolean,
+        setComponentNameForConfirmDeviceCredentialActivity: Boolean = false
+    ) {
         setUserCredentialType(isPassword = true)
 
         val confirmationRequired = true
         val info =
-            PromptInfo().apply {
-                title = TITLE
-                subtitle = SUBTITLE
-                description = DESCRIPTION
-                negativeButtonText = NEGATIVE_TEXT
+            basicPromptInfo().apply {
                 isConfirmationRequested = confirmationRequired
                 authenticators =
                     if (allowCredentialFallback) {
@@ -100,26 +133,27 @@
                         Authenticators.BIOMETRIC_STRONG
                     }
                 isDeviceCredentialAllowed = allowCredentialFallback
+                componentNameForConfirmDeviceCredentialActivity =
+                    if (setComponentNameForConfirmDeviceCredentialActivity)
+                        componentNameOverriddenForConfirmDeviceCredentialActivity
+                    else null
             }
-        val modalities =
-            BiometricModalities(
-                fingerprintProperties = fingerprintSensorPropertiesInternal().first(),
-                faceProperties = faceSensorPropertiesInternal().first(),
-            )
 
         val currentPrompt by collectLastValue(interactor.prompt)
-        val credentialKind by collectLastValue(interactor.credentialKind)
+        val promptKind by collectLastValue(interactor.promptKind)
         val isCredentialAllowed by collectLastValue(interactor.isCredentialAllowed)
-        val isExplicitConfirmationRequired by collectLastValue(interactor.isConfirmationRequired)
+        val credentialKind by collectLastValue(interactor.credentialKind)
+        val isConfirmationRequired by collectLastValue(interactor.isConfirmationRequired)
 
         assertThat(currentPrompt).isNull()
 
-        interactor.useBiometricsForAuthentication(
+        interactor.setPrompt(
             info,
             USER_ID,
-            CHALLENGE,
             modalities,
-            OP_PACKAGE_NAME
+            CHALLENGE,
+            OP_PACKAGE_NAME,
+            false /*onSwitchToCredential*/
         )
 
         assertThat(currentPrompt).isNotNull()
@@ -128,36 +162,179 @@
         assertThat(currentPrompt?.subtitle).isEqualTo(SUBTITLE)
         assertThat(currentPrompt?.negativeButtonText).isEqualTo(NEGATIVE_TEXT)
         assertThat(currentPrompt?.opPackageName).isEqualTo(OP_PACKAGE_NAME)
+        assertThat(promptKind!!.isBiometric()).isTrue()
+        assertThat(currentPrompt?.componentNameForConfirmDeviceCredentialActivity)
+            .isEqualTo(
+                if (setComponentNameForConfirmDeviceCredentialActivity)
+                    componentNameOverriddenForConfirmDeviceCredentialActivity
+                else null
+            )
 
         if (allowCredentialFallback) {
             assertThat(credentialKind).isSameInstanceAs(PromptKind.Password)
             assertThat(isCredentialAllowed).isTrue()
         } else {
-            assertThat(credentialKind).isEqualTo(PromptKind.Biometric())
+            assertThat(credentialKind).isEqualTo(PromptKind.None)
             assertThat(isCredentialAllowed).isFalse()
         }
-        assertThat(isExplicitConfirmationRequired).isEqualTo(confirmationRequired)
+        assertThat(isConfirmationRequired).isEqualTo(confirmationRequired)
 
         interactor.resetPrompt()
         verifyUnset()
     }
 
     @Test
-    fun usePinCredentialAndReset() =
-        testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PIN) }
+    fun usePinCredentialAndReset() = testScope.runTest { useCredentialAndReset(PromptKind.Pin) }
 
     @Test
     fun usePatternCredentialAndReset() =
-        testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PATTERN) }
+        testScope.runTest { useCredentialAndReset(PromptKind.Pattern) }
 
     @Test
     fun usePasswordCredentialAndReset() =
-        testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PASSWORD) }
+        testScope.runTest { useCredentialAndReset(PromptKind.Password) }
 
-    private fun TestScope.useCredentialAndReset(@Utils.CredentialType kind: Int) {
+    @Test
+    fun promptKind_isBiometric_whenBiometricAllowed() =
+        testScope.runTest {
+            setUserCredentialType(isPassword = true)
+            val info = basicPromptInfo()
+
+            val promptKind by collectLastValue(interactor.promptKind)
+            assertThat(promptKind).isEqualTo(PromptKind.None)
+
+            interactor.setPrompt(
+                info,
+                USER_ID,
+                modalities,
+                CHALLENGE,
+                OP_PACKAGE_NAME,
+                false /*onSwitchToCredential*/
+            )
+
+            assertThat(promptKind?.isBiometric()).isTrue()
+
+            interactor.resetPrompt()
+            verifyUnset()
+        }
+
+    @Test
+    fun promptKind_isCredential_onSwitchToCredential() =
+        testScope.runTest {
+            setUserCredentialType(isPassword = true)
+            val info = basicPromptInfo()
+
+            val promptKind by collectLastValue(interactor.promptKind)
+            assertThat(promptKind).isEqualTo(PromptKind.None)
+
+            interactor.setPrompt(
+                info,
+                USER_ID,
+                modalities,
+                CHALLENGE,
+                OP_PACKAGE_NAME,
+                true /*onSwitchToCredential*/
+            )
+
+            assertThat(promptKind).isEqualTo(PromptKind.Password)
+
+            interactor.resetPrompt()
+            verifyUnset()
+        }
+
+    @Test
+    fun promptKind_isCredential_whenBiometricIsNotAllowed() =
+        testScope.runTest {
+            setUserCredentialType(isPassword = true)
+            val info =
+                basicPromptInfo().apply {
+                    isDeviceCredentialAllowed = true
+                    authenticators = Authenticators.DEVICE_CREDENTIAL
+                }
+
+            val promptKind by collectLastValue(interactor.promptKind)
+            assertThat(promptKind).isEqualTo(PromptKind.None)
+
+            interactor.setPrompt(
+                info,
+                USER_ID,
+                modalities,
+                CHALLENGE,
+                OP_PACKAGE_NAME,
+                false /*onSwitchToCredential*/
+            )
+
+            assertThat(promptKind).isEqualTo(PromptKind.Password)
+
+            interactor.resetPrompt()
+            verifyUnset()
+        }
+
+    @Test
+    fun promptKind_isCredential_whenBiometricIsNotAllowed_withMoreOptionsButton() =
+        testScope.runTest {
+            setUserCredentialType(isPassword = true)
+            val info =
+                basicPromptInfo().apply {
+                    isDeviceCredentialAllowed = true
+                    authenticators = Authenticators.DEVICE_CREDENTIAL
+                    contentView =
+                        PromptContentViewWithMoreOptionsButton.Builder()
+                            .setMoreOptionsButtonListener(fakeExecutor) { _, _ -> }
+                            .build()
+                }
+
+            val promptKind by collectLastValue(interactor.promptKind)
+            assertThat(promptKind).isEqualTo(PromptKind.None)
+
+            interactor.setPrompt(
+                info,
+                USER_ID,
+                modalities,
+                CHALLENGE,
+                OP_PACKAGE_NAME,
+                false /*onSwitchToCredential*/
+            )
+
+            assertThat(promptKind).isEqualTo(PromptKind.Password)
+
+            interactor.resetPrompt()
+            verifyUnset()
+        }
+
+    @Test
+    fun promptKind_isBiometric_whenBiometricIsNotAllowed_withVerticalList() =
+        testScope.runTest {
+            setUserCredentialType(isPassword = true)
+            val info =
+                basicPromptInfo().apply {
+                    isDeviceCredentialAllowed = true
+                    authenticators = Authenticators.DEVICE_CREDENTIAL
+                    contentView = PromptVerticalListContentView.Builder().build()
+                }
+
+            val promptKind by collectLastValue(interactor.promptKind)
+            assertThat(promptKind).isEqualTo(PromptKind.None)
+
+            interactor.setPrompt(
+                info,
+                USER_ID,
+                modalities,
+                CHALLENGE,
+                OP_PACKAGE_NAME,
+                false /*onSwitchToCredential*/
+            )
+
+            assertThat(promptKind?.isBiometric()).isTrue()
+
+            interactor.resetPrompt()
+            verifyUnset()
+        }
+
+    private fun TestScope.useCredentialAndReset(kind: PromptKind) {
         setUserCredentialType(
-            isPin = kind == Utils.CREDENTIAL_PIN,
-            isPassword = kind == Utils.CREDENTIAL_PASSWORD,
+            isPin = kind == PromptKind.Pin,
+            isPassword = kind == PromptKind.Password,
         )
 
         val info =
@@ -175,11 +352,18 @@
 
         assertThat(currentPrompt).isNull()
 
-        interactor.useCredentialsForAuthentication(info, kind, USER_ID, CHALLENGE, OP_PACKAGE_NAME)
+        interactor.setPrompt(
+            info,
+            USER_ID,
+            BiometricModalities(),
+            CHALLENGE,
+            OP_PACKAGE_NAME,
+            false /*onSwitchToCredential*/
+        )
 
         // not using biometrics, should be null with no fallback option
         assertThat(currentPrompt).isNull()
-        assertThat(credentialKind).isEqualTo(PromptKind.Biometric())
+        assertThat(credentialKind).isEqualTo(PromptKind.None)
 
         interactor.resetPrompt()
         verifyUnset()
@@ -187,13 +371,16 @@
 
     private fun TestScope.verifyUnset() {
         val currentPrompt by collectLastValue(interactor.prompt)
+        val promptKind by collectLastValue(interactor.promptKind)
+        val isCredentialAllowed by collectLastValue(interactor.isCredentialAllowed)
         val credentialKind by collectLastValue(interactor.credentialKind)
+        val isConfirmationRequired by collectLastValue(interactor.isConfirmationRequired)
 
         assertThat(currentPrompt).isNull()
-
-        val kind = credentialKind as? PromptKind.Biometric
-        assertThat(kind).isNotNull()
-        assertThat(kind?.activeModalities?.isEmpty).isTrue()
+        assertThat(promptKind).isEqualTo(PromptKind.None)
+        assertThat(isCredentialAllowed).isFalse()
+        assertThat(credentialKind).isEqualTo(PromptKind.None)
+        assertThat(isConfirmationRequired).isFalse()
     }
 
     private fun setUserCredentialType(isPin: Boolean = false, isPassword: Boolean = false) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
index 5caa146..0d01472 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
@@ -16,97 +16,95 @@
 
 package com.android.systemui.biometrics.ui.viewmodel
 
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.BrokenWithSceneContainer
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.flags.parameterizeSceneContainerFlag
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.android.systemui.user.domain.UserDomainLayerModule
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.statusbar.phone.systemUIDialogManager
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
 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
-import org.junit.runners.JUnit4
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
-class DefaultUdfpsTouchOverlayViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class DefaultUdfpsTouchOverlayViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
+
+    private val kosmos =
+        testKosmos().apply {
+            fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }
+        }
+    private val testScope = kosmos.testScope
+
     @Captor
     private lateinit var sysuiDialogListenerCaptor: ArgumentCaptor<SystemUIDialogManager.Listener>
-    private var systemUIDialogManager: SystemUIDialogManager = mock()
+    private var systemUIDialogManager = kosmos.systemUIDialogManager
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+    private lateinit var underTest: DefaultUdfpsTouchOverlayViewModel
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return parameterizeSceneContainerFlag()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        underTest =
+            DefaultUdfpsTouchOverlayViewModel(
+                kosmos.shadeInteractor,
+                systemUIDialogManager,
+            )
     }
 
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                UserDomainLayerModule::class,
-                BiometricsDomainLayerModule::class,
-            ]
-    )
-    interface TestComponent : SysUITestComponent<DefaultUdfpsTouchOverlayViewModel> {
-        val keyguardRepository: FakeKeyguardRepository
-        val shadeRepository: FakeShadeRepository
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-                featureFlags: FakeFeatureFlagsClassicModule,
-                mocks: TestMocksModule,
-            ): TestComponent
-        }
-    }
-
-    private fun TestComponent.shadeExpanded(expanded: Boolean) {
+    private fun shadeExpanded(expanded: Boolean) {
         if (expanded) {
-            shadeRepository.setLegacyShadeExpansion(1f)
-            shadeRepository.setLegacyShadeTracking(false)
-            shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(true)
+            shadeTestUtil.setShadeExpansion(1f)
+            shadeTestUtil.setTracking(false)
+            shadeTestUtil.setLegacyExpandedOrAwaitingInputTransfer(true)
         } else {
             keyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setLegacyShadeTracking(false)
-            shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(false)
+            shadeTestUtil.setShadeExpansion(0f)
+            shadeTestUtil.setTracking(false)
+            shadeTestUtil.setLegacyExpandedOrAwaitingInputTransfer(false)
         }
     }
 
-    private val testComponent: TestComponent =
-        DaggerDefaultUdfpsTouchOverlayViewModelTest_TestComponent.factory()
-            .create(
-                test = this,
-                featureFlags =
-                    FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) },
-                mocks = TestMocksModule(systemUIDialogManager = systemUIDialogManager),
-            )
-
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun shadeNotExpanded_noDialogShowing_shouldHandleTouchesTrue() =
-        testComponent.runTest {
+        testScope.runTest {
             val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
             runCurrent()
 
@@ -120,7 +118,7 @@
 
     @Test
     fun shadeNotExpanded_dialogShowing_shouldHandleTouchesFalse() =
-        testComponent.runTest {
+        testScope.runTest {
             val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
             runCurrent()
 
@@ -134,7 +132,7 @@
 
     @Test
     fun shadeExpanded_noDialogShowing_shouldHandleTouchesFalse() =
-        testComponent.runTest {
+        testScope.runTest {
             val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
             runCurrent()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index a732418..fa78f0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -16,9 +16,13 @@
 
 package com.android.systemui.biometrics.ui.viewmodel
 
+import android.app.ActivityManager.RunningTaskInfo
 import android.app.ActivityTaskManager
+import android.content.ComponentName
+import android.content.pm.ActivityInfo
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
+import android.content.pm.PackageManager.NameNotFoundException
 import android.content.res.Configuration
 import android.graphics.Bitmap
 import android.graphics.Point
@@ -33,10 +37,12 @@
 import android.hardware.face.FaceSensorPropertiesInternal
 import android.hardware.fingerprint.FingerprintSensorProperties
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.platform.test.annotations.EnableFlags
 import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
+import com.android.launcher3.icons.IconProvider
 import com.android.systemui.Flags.FLAG_BP_TALKBACK
 import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
 import com.android.systemui.SysuiTestCase
@@ -94,6 +100,7 @@
 private const val DELAY = 1000L
 private const val OP_PACKAGE_NAME = "biometric.testapp"
 private const val OP_PACKAGE_NAME_NO_ICON = "biometric.testapp.noicon"
+private const val OP_PACKAGE_NAME_CAN_NOT_BE_FOUND = "can.not.be.found"
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -107,18 +114,23 @@
     @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
     @Mock private lateinit var udfpsUtils: UdfpsUtils
     @Mock private lateinit var packageManager: PackageManager
+    @Mock private lateinit var iconProvider: IconProvider
     @Mock private lateinit var applicationInfoWithIcon: ApplicationInfo
     @Mock private lateinit var applicationInfoNoIcon: ApplicationInfo
     @Mock private lateinit var activityTaskManager: ActivityTaskManager
+    @Mock private lateinit var activityInfo: ActivityInfo
+    @Mock private lateinit var runningTaskInfo: RunningTaskInfo
 
     private val fakeExecutor = FakeExecutor(FakeSystemClock())
     private val testScope = TestScope()
     private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
+    private val defaultLogoIconWithOverrides = context.getDrawable(R.drawable.ic_add)
     private val logoResFromApp = R.drawable.ic_cake
     private val logoFromApp = context.getDrawable(logoResFromApp)
     private val logoBitmapFromApp = Bitmap.createBitmap(400, 400, Bitmap.Config.RGB_565)
     private val defaultLogoDescription = "Test Android App"
     private val logoDescriptionFromApp = "Test Cake App"
+    private val packageNameForLogoWithOverrides = "should.use.overridden.logo"
 
     private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
     private lateinit var promptRepository: FakePromptRepository
@@ -169,11 +181,12 @@
             )
         biometricStatusRepository = FakeBiometricStatusRepository()
         biometricStatusInteractor =
-            BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository,
-                fingerprintRepository)
-        selector =
-            PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
-        selector.resetPrompt()
+            BiometricStatusInteractorImpl(
+                activityTaskManager,
+                biometricStatusRepository,
+                fingerprintRepository
+            )
+
         promptContentView =
             PromptVerticalListContentView.Builder()
                 .addListItem(PromptContentItemBulletedText("content item 1"))
@@ -183,32 +196,35 @@
         promptContentViewWithMoreOptionsButton =
             PromptContentViewWithMoreOptionsButton.Builder()
                 .setDescription("test")
-                .setMoreOptionsButtonListener(fakeExecutor, { _, _ -> })
+                .setMoreOptionsButtonListener(fakeExecutor) { _, _ -> }
                 .build()
 
-        viewModel =
-            PromptViewModel(
-                displayStateInteractor,
-                selector,
-                mContext,
-                udfpsOverlayInteractor,
-                biometricStatusInteractor,
-                udfpsUtils
-            )
-        iconViewModel = viewModel.iconViewModel
-
-        // Set up default logo icon and app customized icon
+        // Set up default logo info and app customized info
         whenever(packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME_NO_ICON), anyInt()))
             .thenReturn(applicationInfoNoIcon)
         whenever(packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME), anyInt()))
             .thenReturn(applicationInfoWithIcon)
+        whenever(packageManager.getApplicationInfo(eq(packageNameForLogoWithOverrides), anyInt()))
+            .thenReturn(applicationInfoWithIcon)
+        whenever(packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME_CAN_NOT_BE_FOUND), anyInt()))
+            .thenThrow(NameNotFoundException())
+
+        whenever(packageManager.getActivityInfo(any(), anyInt())).thenReturn(activityInfo)
+        whenever(iconProvider.getIcon(activityInfo)).thenReturn(defaultLogoIconWithOverrides)
         whenever(packageManager.getApplicationIcon(applicationInfoWithIcon))
             .thenReturn(defaultLogoIcon)
         whenever(packageManager.getApplicationLabel(applicationInfoWithIcon))
             .thenReturn(defaultLogoDescription)
+        whenever(packageManager.getUserBadgedIcon(any(), any())).then { it.getArgument(0) }
+        whenever(packageManager.getUserBadgedLabel(any(), any())).then { it.getArgument(0) }
+
         context.setMockPackageManager(packageManager)
         val resources = context.getOrCreateTestableResources()
         resources.addOverride(logoResFromApp, logoFromApp)
+        resources.addOverride(
+            R.array.biometric_dialog_package_names_for_logo_with_overrides,
+            arrayOf(packageNameForLogoWithOverrides)
+        )
     }
 
     @Test
@@ -1257,8 +1273,8 @@
     }
 
     @Test
+    @EnableFlags(FLAG_BP_TALKBACK)
     fun hint_for_talkback_guidance() = runGenericTest {
-        mSetFlagsRule.enableFlags(FLAG_BP_TALKBACK)
         val hint by collectLastValue(viewModel.accessibilityHint)
 
         // Touches should fall outside of sensor area
@@ -1280,10 +1296,9 @@
     }
 
     @Test
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
     fun descriptionOverriddenByVerticalListContentView() =
         runGenericTest(contentView = promptContentView, description = "test description") {
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val contentView by collectLastValue(viewModel.contentView)
             val description by collectLastValue(viewModel.description)
 
@@ -1292,13 +1307,12 @@
         }
 
     @Test
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
     fun descriptionOverriddenByContentViewWithMoreOptionsButton() =
         runGenericTest(
             contentView = promptContentViewWithMoreOptionsButton,
             description = "test description"
         ) {
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val contentView by collectLastValue(viewModel.contentView)
             val description by collectLastValue(viewModel.description)
 
@@ -1307,10 +1321,9 @@
         }
 
     @Test
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
     fun descriptionWithoutContentView() =
         runGenericTest(description = "test description") {
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val contentView by collectLastValue(viewModel.contentView)
             val description by collectLastValue(viewModel.description)
 
@@ -1319,62 +1332,84 @@
         }
 
     @Test
-    fun logoIsNullIfPackageNameNotFound() =
-        runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    fun logo_nullIfPkgNameNotFound() =
+        runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) {
             val logo by collectLastValue(viewModel.logo)
             assertThat(logo).isNull()
         }
 
     @Test
-    fun defaultLogoIfNoLogoSet() = runGenericTest {
-        mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-        mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    fun logo_defaultWithOverrides() =
+        runGenericTest(packageName = packageNameForLogoWithOverrides) {
+            val logo by collectLastValue(viewModel.logo)
+
+            // 1. PM.getApplicationInfo(packageNameForLogoWithOverrides) is set to return
+            // applicationInfoWithIcon with defaultLogoIcon,
+            // 2. iconProvider.getIcon() is set to return defaultLogoIconForGMSCore
+            // For the apps with packageNameForLogoWithOverrides, 2 should be called instead of 1
+            assertThat(logo).isEqualTo(defaultLogoIconWithOverrides)
+        }
+
+    @Test
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    fun logo_defaultIsNull() =
+        runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
+            val logo by collectLastValue(viewModel.logo)
+            assertThat(logo).isNull()
+        }
+
+    @Test
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    fun logo_default() = runGenericTest {
         val logo by collectLastValue(viewModel.logo)
         assertThat(logo).isEqualTo(defaultLogoIcon)
     }
 
     @Test
-    fun logoResSetByApp() =
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    fun logo_resSetByApp() =
         runGenericTest(logoRes = logoResFromApp) {
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val logo by collectLastValue(viewModel.logo)
             assertThat(logo).isEqualTo(logoFromApp)
         }
 
     @Test
-    fun logoBitmapSetByApp() =
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    fun logo_bitmapSetByApp() =
         runGenericTest(logoBitmap = logoBitmapFromApp) {
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val logo by collectLastValue(viewModel.logo)
             assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
         }
 
     @Test
-    fun logoDescriptionIsEmptyIfPackageNameNotFound() =
-        runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    fun logoDescription_emptyIfPkgNameNotFound() =
+        runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) {
             val logoDescription by collectLastValue(viewModel.logoDescription)
             assertThat(logoDescription).isEqualTo("")
         }
 
     @Test
-    fun defaultLogoDescriptionIfNoLogoDescriptionSet() = runGenericTest {
-        mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-        mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    fun logoDescription_defaultIsEmpty() =
+        runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
+            val logoDescription by collectLastValue(viewModel.logoDescription)
+            assertThat(logoDescription).isEqualTo("")
+        }
+
+    @Test
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    fun logoDescription_default() = runGenericTest {
         val logoDescription by collectLastValue(viewModel.logoDescription)
         assertThat(logoDescription).isEqualTo(defaultLogoDescription)
     }
 
     @Test
-    fun logoDescriptionSetByApp() =
+    @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+    fun logoDescription_setByApp() =
         runGenericTest(logoDescription = logoDescriptionFromApp) {
-            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-            mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
             val logoDescription by collectLastValue(viewModel.logoDescription)
             assertThat(logoDescription).isEqualTo(logoDescriptionFromApp)
         }
@@ -1417,6 +1452,26 @@
         packageName: String = OP_PACKAGE_NAME,
         block: suspend TestScope.() -> Unit,
     ) {
+        val topActivity = ComponentName(packageName, "test app")
+        runningTaskInfo.topActivity = topActivity
+        whenever(activityTaskManager.getTasks(1)).thenReturn(listOf(runningTaskInfo))
+        selector =
+            PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
+        selector.resetPrompt()
+
+        viewModel =
+            PromptViewModel(
+                displayStateInteractor,
+                selector,
+                mContext,
+                udfpsOverlayInteractor,
+                biometricStatusInteractor,
+                udfpsUtils,
+                iconProvider,
+                activityTaskManager
+            )
+        iconViewModel = viewModel.iconViewModel
+
         selector.initializePrompt(
             requireConfirmation = testCase.confirmationRequested,
             allowCredentialFallback = allowCredentialFallback,
@@ -1630,12 +1685,13 @@
             isConfirmationRequested = requireConfirmation
         }
 
-    useBiometricsForAuthentication(
+    setPrompt(
         info,
         USER_ID,
-        CHALLENGE,
         BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
+        CHALLENGE,
         packageName,
+        false /*onUseDeviceCredential*/
     )
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
index a569cee..49f2043 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
@@ -21,7 +21,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
@@ -92,7 +92,7 @@
         when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null);
 
-        when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
+        when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
         when(mSystemUIDialogFactory.create(any(), any())).thenReturn(mDialog);
 
         mBroadcastDialogDelegate = new BroadcastDialogDelegate(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
index 62c98b0..7215619 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
@@ -50,7 +50,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.Mock
 import org.mockito.Mockito.`when`
 import org.mockito.junit.MockitoJUnit
@@ -104,7 +104,7 @@
         dispatcher = UnconfinedTestDispatcher(scheduler)
         testScope = TestScope(dispatcher)
 
-        whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
+        whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState)
 
         mBluetoothTileDialogDelegate =
             BluetoothTileDialogDelegate(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
index af1d315..11f74c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.bluetooth.qsdialog
 
 import android.bluetooth.BluetoothAdapter
+import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
@@ -63,6 +64,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
+@EnableFlags(Flags.FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE)
 class BluetoothTileDialogViewModelTest : SysuiTestCase() {
 
     @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
@@ -77,6 +79,8 @@
 
     @Mock private lateinit var deviceItemInteractor: DeviceItemInteractor
 
+    @Mock private lateinit var deviceItemActionInteractor: DeviceItemActionInteractor
+
     @Mock private lateinit var activityStarter: ActivityStarter
 
     @Mock private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator
@@ -111,13 +115,13 @@
 
     @Before
     fun setUp() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE)
         scheduler = TestCoroutineScheduler()
         dispatcher = UnconfinedTestDispatcher(scheduler)
         testScope = TestScope(dispatcher)
         bluetoothTileDialogViewModel =
             BluetoothTileDialogViewModel(
                 deviceItemInteractor,
+                deviceItemActionInteractor,
                 BluetoothStateInteractor(
                     localBluetoothManager,
                     bluetoothTileDialogLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorImplTest.kt
new file mode 100644
index 0000000..762137b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorImplTest.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.bluetooth.qsdialog
+
+import android.bluetooth.BluetoothDevice
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+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.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@OptIn(ExperimentalCoroutinesApi::class)
+class DeviceItemActionInteractorImplTest : SysuiTestCase() {
+    @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+    private val kosmos = testKosmos().apply { testDispatcher = UnconfinedTestDispatcher() }
+    private lateinit var actionInteractorImpl: DeviceItemActionInteractor
+
+    @Mock private lateinit var dialog: SystemUIDialog
+    @Mock private lateinit var cachedDevice: CachedBluetoothDevice
+    @Mock private lateinit var device: BluetoothDevice
+    @Mock private lateinit var deviceItem: DeviceItem
+
+    @Before
+    fun setUp() {
+        actionInteractorImpl = kosmos.deviceItemActionInteractor
+        whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedDevice)
+        whenever(cachedDevice.address).thenReturn("ADDRESS")
+        whenever(cachedDevice.device).thenReturn(device)
+    }
+
+    @Test
+    fun testOnClick_connectedMedia_setActive() {
+        with(kosmos) {
+            testScope.runTest {
+                whenever(deviceItem.type)
+                    .thenReturn(DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE)
+                actionInteractorImpl.onClick(deviceItem, dialog)
+                verify(cachedDevice).setActive()
+                verify(bluetoothTileDialogLogger)
+                    .logDeviceClick(
+                        cachedDevice.address,
+                        DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE
+                    )
+            }
+        }
+    }
+
+    @Test
+    fun testOnClick_activeMedia_disconnect() {
+        with(kosmos) {
+            testScope.runTest {
+                whenever(deviceItem.type).thenReturn(DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
+                actionInteractorImpl.onClick(deviceItem, dialog)
+                verify(cachedDevice).disconnect()
+                verify(bluetoothTileDialogLogger)
+                    .logDeviceClick(
+                        cachedDevice.address,
+                        DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE
+                    )
+            }
+        }
+    }
+
+    @Test
+    fun testOnClick_connectedOtherDevice_disconnect() {
+        with(kosmos) {
+            testScope.runTest {
+                whenever(deviceItem.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
+                actionInteractorImpl.onClick(deviceItem, dialog)
+                verify(cachedDevice).disconnect()
+                verify(bluetoothTileDialogLogger)
+                    .logDeviceClick(cachedDevice.address, DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
+            }
+        }
+    }
+
+    @Test
+    fun testOnClick_saved_connect() {
+        with(kosmos) {
+            testScope.runTest {
+                whenever(deviceItem.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE)
+                actionInteractorImpl.onClick(deviceItem, dialog)
+                verify(cachedDevice).connect()
+                verify(bluetoothTileDialogLogger)
+                    .logDeviceClick(cachedDevice.address, DeviceItemType.SAVED_BLUETOOTH_DEVICE)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt
new file mode 100644
index 0000000..e8e37bc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.bluetooth.qsdialog
+
+import com.android.internal.logging.uiEventLogger
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.bluetoothTileDialogLogger: BluetoothTileDialogLogger by Kosmos.Fixture { mock {} }
+
+val Kosmos.deviceItemActionInteractor: DeviceItemActionInteractor by
+    Kosmos.Fixture {
+        DeviceItemActionInteractorImpl(
+            testDispatcher,
+            bluetoothTileDialogLogger,
+            uiEventLogger,
+        )
+    }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
index 28cbcb4..4bcd9a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.bluetooth.qsdialog
 
 import android.bluetooth.BluetoothDevice
-import android.content.pm.PackageInfo
+import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.media.AudioManager
 import android.platform.test.annotations.DisableFlags
@@ -25,7 +25,6 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
-import com.android.settingslib.bluetooth.BluetoothUtils
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.flags.Flags
 import com.android.systemui.SysuiTestCase
@@ -120,11 +119,10 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
     fun testSavedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() {
-        val exclusiveManagerName =
-            BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
         `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
-            .thenReturn(exclusiveManagerName.toByteArray())
-        `when`(packageManager.getPackageInfo(exclusiveManagerName, 0)).thenReturn(PackageInfo())
+            .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+        `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
+            .thenReturn(ApplicationInfo())
         `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
         `when`(cachedDevice.isConnected).thenReturn(false)
 
@@ -144,11 +142,11 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
-    fun testSavedFactory_isFilterMatched_notAllowedExclusiveManager_returnsTrue() {
+    fun testSavedFactory_isFilterMatched_exclusiveManagerNotEnabled_returnsTrue() {
         `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
-            .thenReturn(FAKE_EXCLUSIVE_MANAGER_NAME.toByteArray())
-        `when`(packageManager.getPackageInfo(FAKE_EXCLUSIVE_MANAGER_NAME, 0))
-            .thenReturn(PackageInfo())
+            .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+        `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
+            .thenReturn(ApplicationInfo().also { it.enabled = false })
         `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
         `when`(cachedDevice.isConnected).thenReturn(false)
 
@@ -158,12 +156,10 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
-    fun testSavedFactory_isFilterMatched_uninstalledExclusiveManager_returnsTrue() {
-        val exclusiveManagerName =
-            BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
+    fun testSavedFactory_isFilterMatched_exclusiveManagerNotInstalled_returnsTrue() {
         `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
-            .thenReturn(exclusiveManagerName.toByteArray())
-        `when`(packageManager.getPackageInfo(exclusiveManagerName, 0))
+            .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+        `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
             .thenThrow(PackageManager.NameNotFoundException("Test!"))
         `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
         `when`(cachedDevice.isConnected).thenReturn(false)
@@ -228,11 +224,10 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
     fun testConnectedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() {
-        val exclusiveManagerName =
-            BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
         `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
-            .thenReturn(exclusiveManagerName.toByteArray())
-        `when`(packageManager.getPackageInfo(exclusiveManagerName, 0)).thenReturn(PackageInfo())
+            .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+        `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
+            .thenReturn(ApplicationInfo())
         `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
         `when`(bluetoothDevice.isConnected).thenReturn(true)
         audioManager.setMode(AudioManager.MODE_NORMAL)
@@ -254,11 +249,11 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
-    fun testConnectedFactory_isFilterMatched_notAllowedExclusiveManager_returnsTrue() {
+    fun testConnectedFactory_isFilterMatched_exclusiveManagerNotEnabled_returnsTrue() {
         `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
-            .thenReturn(FAKE_EXCLUSIVE_MANAGER_NAME.toByteArray())
-        `when`(packageManager.getPackageInfo(FAKE_EXCLUSIVE_MANAGER_NAME, 0))
-            .thenReturn(PackageInfo())
+            .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+        `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
+            .thenReturn(ApplicationInfo().also { it.enabled = false })
         `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
         `when`(bluetoothDevice.isConnected).thenReturn(true)
         audioManager.setMode(AudioManager.MODE_NORMAL)
@@ -269,12 +264,10 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
-    fun testConnectedFactory_isFilterMatched_uninstalledExclusiveManager_returnsTrue() {
-        val exclusiveManagerName =
-            BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
+    fun testConnectedFactory_isFilterMatched_exclusiveManagerNotInstalled_returnsTrue() {
         `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
-            .thenReturn(exclusiveManagerName.toByteArray())
-        `when`(packageManager.getPackageInfo(exclusiveManagerName, 0))
+            .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+        `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
             .thenThrow(PackageManager.NameNotFoundException("Test!"))
         `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
         `when`(bluetoothDevice.isConnected).thenReturn(true)
@@ -317,7 +310,7 @@
     companion object {
         const val DEVICE_NAME = "DeviceName"
         const val CONNECTION_SUMMARY = "ConnectionSummary"
-        private const val FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name"
+        private const val TEST_EXCLUSIVE_MANAGER = "com.test.manager"
         private const val DEVICE_ADDRESS = "04:52:C7:0B:D8:3C"
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
index daf4a3c..2b4f950 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
@@ -23,7 +23,6 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
-import com.android.internal.logging.UiEventLogger
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.systemui.SysuiTestCase
@@ -39,7 +38,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
@@ -71,8 +69,6 @@
 
     @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
 
-    @Mock private lateinit var uiEventLogger: UiEventLogger
-
     @Mock private lateinit var logger: BluetoothTileDialogLogger
 
     private val fakeSystemClock = FakeSystemClock()
@@ -94,7 +90,6 @@
                 adapter,
                 localBluetoothManager,
                 fakeSystemClock,
-                uiEventLogger,
                 logger,
                 testScope.backgroundScope,
                 dispatcher
@@ -218,61 +213,6 @@
         }
     }
 
-    @Test
-    fun testUpdateDeviceItemOnClick_connectedMedia_setActive() {
-        testScope.runTest {
-            `when`(deviceItem1.type).thenReturn(DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE)
-
-            interactor.updateDeviceItemOnClick(deviceItem1)
-
-            verify(cachedDevice1).setActive()
-            verify(logger)
-                .logDeviceClick(
-                    cachedDevice1.address,
-                    DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE
-                )
-        }
-    }
-
-    @Test
-    fun testUpdateDeviceItemOnClick_activeMedia_disconnect() {
-        testScope.runTest {
-            `when`(deviceItem1.type).thenReturn(DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
-
-            interactor.updateDeviceItemOnClick(deviceItem1)
-
-            verify(cachedDevice1).disconnect()
-            verify(logger)
-                .logDeviceClick(cachedDevice1.address, DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
-        }
-    }
-
-    @Test
-    fun testUpdateDeviceItemOnClick_connectedOtherDevice_disconnect() {
-        testScope.runTest {
-            `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
-
-            interactor.updateDeviceItemOnClick(deviceItem1)
-
-            verify(cachedDevice1).disconnect()
-            verify(logger)
-                .logDeviceClick(cachedDevice1.address, DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
-        }
-    }
-
-    @Test
-    fun testUpdateDeviceItemOnClick_saved_connect() {
-        testScope.runTest {
-            `when`(deviceItem1.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE)
-
-            interactor.updateDeviceItemOnClick(deviceItem1)
-
-            verify(cachedDevice1).connect()
-            verify(logger)
-                .logDeviceClick(cachedDevice1.address, DeviceItemType.SAVED_BLUETOOTH_DEVICE)
-        }
-    }
-
     private fun createFactory(
         isFilterMatchFunc: (CachedBluetoothDevice) -> Boolean,
         deviceItem: DeviceItem
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index bc6c459..5361cef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -35,9 +35,13 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.communal.domain.interactor.CommunalInteractor;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerFake;
+import com.android.systemui.flags.DisableSceneContainer;
+import com.android.systemui.flags.EnableSceneContainer;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -50,6 +54,7 @@
 import com.android.systemui.util.sensors.ThresholdSensor;
 import com.android.systemui.util.time.FakeSystemClock;
 
+import kotlinx.coroutines.flow.MutableStateFlow;
 import kotlinx.coroutines.flow.StateFlowKt;
 
 import org.junit.Before;
@@ -89,6 +94,14 @@
     private SelectedUserInteractor mSelectedUserInteractor;
     @Mock
     private CommunalInteractor mCommunalInteractor;
+    @Mock
+    private DeviceEntryInteractor mDeviceEntryInteractor;
+    private final MutableStateFlow<Boolean> mIsDeviceEntered =
+            StateFlowKt.MutableStateFlow(false);
+    @Mock
+    private SceneContainerOcclusionInteractor mSceneContainerOcclusionInteractor;
+    private final MutableStateFlow<Boolean> mIsInvisibleDueToOcclusion =
+            StateFlowKt.MutableStateFlow(false);
     private final DockManagerFake mDockManager = new DockManagerFake();
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
@@ -99,15 +112,21 @@
 
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(false);
         when(mShadeInteractor.isQsExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
 
+        when(mDeviceEntryInteractor.isDeviceEntered()).thenReturn(mIsDeviceEntered);
+        when(mSceneContainerOcclusionInteractor.getInvisibleDueToOcclusion()).thenReturn(
+                mIsInvisibleDueToOcclusion);
+
         mFalsingCollector = new FalsingCollectorImpl(mFalsingDataProvider, mFalsingManager,
                 mKeyguardUpdateMonitor, mHistoryTracker, mProximitySensor,
                 mStatusBarStateController, mKeyguardStateController,
                 () -> mShadeInteractor, mBatteryController,
                 mDockManager, mFakeExecutor,
                 mJavaAdapter, mFakeSystemClock, () -> mSelectedUserInteractor,
-                () -> mCommunalInteractor
+                () -> mCommunalInteractor, () -> mDeviceEntryInteractor,
+                () -> mSceneContainerOcclusionInteractor
         );
         mFalsingCollector.init();
     }
@@ -189,7 +208,8 @@
     }
 
     @Test
-    public void testRegisterSensor_OccludingActivity() {
+    @DisableSceneContainer
+    public void testRegisterSensor_OccludingActivity_sceneContainerDisabled() {
         when(mKeyguardStateController.isOccluded()).thenReturn(true);
 
         ArgumentCaptor<StatusBarStateController.StateListener> stateListenerArgumentCaptor =
@@ -203,6 +223,21 @@
     }
 
     @Test
+    @EnableSceneContainer
+    public void testRegisterSensor_OccludingActivity_sceneContainerEnabled() {
+        mIsInvisibleDueToOcclusion.setValue(true);
+
+        ArgumentCaptor<StatusBarStateController.StateListener> stateListenerArgumentCaptor =
+                ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+        verify(mStatusBarStateController).addCallback(stateListenerArgumentCaptor.capture());
+
+        mFalsingCollector.onScreenTurningOn();
+        reset(mProximitySensor);
+        stateListenerArgumentCaptor.getValue().onStateChanged(StatusBarState.SHADE);
+        verify(mProximitySensor).register(any(ThresholdSensor.Listener.class));
+    }
+
+    @Test
     public void testPassThroughEnterKeyEvent() {
         KeyEvent enterDown = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER,
                 0, 0, 0, 0, 0, 0, 0, "");
@@ -280,7 +315,8 @@
     }
 
     @Test
-    public void testAvoidUnlocked() {
+    @DisableSceneContainer
+    public void testAvoidUnlocked_sceneContainerDisabled() {
         MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
         MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
 
@@ -296,6 +332,23 @@
     }
 
     @Test
+    @EnableSceneContainer
+    public void testAvoidUnlocked_sceneContainerEnabled() {
+        MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+        MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
+
+        mIsDeviceEntered.setValue(true);
+
+        // Nothing passed initially
+        mFalsingCollector.onTouchEvent(down);
+        verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
+
+        // Up event would normally flush the up event, but doesn't.
+        mFalsingCollector.onTouchEvent(up);
+        verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
+    }
+
+    @Test
     public void testGestureWhenDozing() {
         // We check the FalsingManager for taps during the transition to AoD (dozing=true,
         // pulsing=false), so the FalsingCollector needs to continue to analyze events that occur
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
index bed05ee..cde7a0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
@@ -30,7 +30,6 @@
 import java.nio.charset.Charset
 import org.junit.After
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -102,12 +101,6 @@
         assertThat(dataRead).isEqualTo(newDataToWrite)
     }
 
-    @Ignore("Ignored until we figure out why it is flaky b/336561027")
-    @Test(expected = FileNotFoundException::class)
-    fun read_fileNotFoundException() {
-        underTest.readBytesFromDisk()
-    }
-
     @Test(expected = FileNotFoundException::class)
     fun clear_returnsTrueWhenFileDeleted() {
         // Write bytes to disk
diff --git a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt
deleted file mode 100644
index ab03465..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.contrast
-
-import android.app.UiModeManager
-import android.app.UiModeManager.ContrastUtils.fromContrastLevel
-import android.os.Looper
-import android.provider.Settings
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import android.view.LayoutInflater
-import android.view.View
-import android.widget.FrameLayout
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.model.SysUiState
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.time.FakeSystemClock
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-/** Test the behaviour of buttons of the [ContrastDialogDelegate]. */
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper
-class ContrastDialogDelegateTest : SysuiTestCase() {
-
-    private val mainExecutor = FakeExecutor(FakeSystemClock())
-    private lateinit var mContrastDialogDelegate: ContrastDialogDelegate
-    @Mock private lateinit var sysuiDialogFactory: SystemUIDialog.Factory
-    @Mock private lateinit var sysuiDialog: SystemUIDialog
-    @Mock private lateinit var mockUiModeManager: UiModeManager
-    @Mock private lateinit var mockUserTracker: UserTracker
-    @Mock private lateinit var mockSecureSettings: SecureSettings
-    @Mock private lateinit var sysuiState: SysUiState
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        mDependency.injectTestDependency(FeatureFlags::class.java, FakeFeatureFlags())
-        mDependency.injectTestDependency(SysUiState::class.java, sysuiState)
-        mDependency.injectMockDependency(DialogTransitionAnimator::class.java)
-        whenever(sysuiState.setFlag(any(), any())).thenReturn(sysuiState)
-        whenever(sysuiDialogFactory.create(any(SystemUIDialog.Delegate::class.java)))
-            .thenReturn(sysuiDialog)
-        whenever(sysuiDialog.layoutInflater).thenReturn(LayoutInflater.from(mContext))
-
-        whenever(mockUserTracker.userId).thenReturn(context.userId)
-        if (Looper.myLooper() == null) Looper.prepare()
-
-        mContrastDialogDelegate =
-            ContrastDialogDelegate(
-                sysuiDialogFactory,
-                mainExecutor,
-                mockUiModeManager,
-                mockUserTracker,
-                mockSecureSettings
-            )
-
-        mContrastDialogDelegate.createDialog()
-        val viewCaptor = ArgumentCaptor.forClass(View::class.java)
-        verify(sysuiDialog).setView(viewCaptor.capture())
-        whenever(sysuiDialog.requireViewById(anyInt()) as View?).then {
-            viewCaptor.value.requireViewById(it.getArgument(0))
-        }
-    }
-
-    @Test
-    fun testClickButtons_putsContrastInSettings() {
-        mContrastDialogDelegate.onCreate(sysuiDialog, null)
-
-        mContrastDialogDelegate.contrastButtons.forEach {
-            (contrastLevel: Int, clickedButton: FrameLayout) ->
-            clickedButton.performClick()
-            mainExecutor.runAllReady()
-            verify(mockSecureSettings)
-                .putFloatForUser(
-                    eq(Settings.Secure.CONTRAST_LEVEL),
-                    eq(fromContrastLevel(contrastLevel)),
-                    eq(context.userId)
-                )
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
index 6a0462b..c39c3fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.deviceentry.domain.ui.viewmodel
 
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.accessibility.data.repository.fakeAccessibilityRepository
@@ -24,7 +24,9 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
 import com.android.systemui.deviceentry.data.ui.viewmodel.deviceEntryUdfpsAccessibilityOverlayViewModel
+import com.android.systemui.deviceentry.ui.viewmodel.DeviceEntryUdfpsAccessibilityOverlayViewModel
 import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
@@ -34,19 +36,22 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.ui.viewmodel.fakeDeviceEntryIconViewModelTransition
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @ExperimentalCoroutinesApi
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class UdfpsAccessibilityOverlayViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class UdfpsAccessibilityOverlayViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
             fakeFeatureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) }
@@ -59,8 +64,27 @@
     private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
     private val deviceEntryFingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository
     private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
-    private val shadeRepository = kosmos.fakeShadeRepository
-    private val underTest = kosmos.deviceEntryUdfpsAccessibilityOverlayViewModel
+
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+    private lateinit var underTest: DeviceEntryUdfpsAccessibilityOverlayViewModel
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
+    @Before
+    fun setup() {
+        underTest = kosmos.deviceEntryUdfpsAccessibilityOverlayViewModel
+    }
 
     @Test
     fun visible() =
@@ -142,7 +166,7 @@
         )
 
         // Shade not expanded
-        shadeRepository.qsExpansion.value = 0f
-        shadeRepository.lockscreenShadeExpansion.value = 0f
+        shadeTestUtil.setQsExpansion(0f)
+        shadeTestUtil.setLockscreenShadeExpansion(0f)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt
new file mode 100644
index 0000000..05a2ca2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt
@@ -0,0 +1,132 @@
+/*
+ * 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.keyboard.shortcut.ui
+
+import android.content.Intent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyboard.shortcut.fakeShortcutHelperStartActivity
+import com.android.systemui.keyboard.shortcut.shortcutHelperActivityStarter
+import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
+import com.android.systemui.keyboard.shortcut.ui.view.ShortcutHelperActivity
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class ShortcutHelperActivityStarterTest : SysuiTestCase() {
+
+    private val kosmos =
+        Kosmos().also {
+            it.testCase = this
+            it.testDispatcher = UnconfinedTestDispatcher()
+        }
+
+    private val testScope = kosmos.testScope
+    private val testHelper = kosmos.shortcutHelperTestHelper
+    private val fakeStartActivity = kosmos.fakeShortcutHelperStartActivity
+    private val starter = kosmos.shortcutHelperActivityStarter
+
+    @Test
+    fun start_doesNotStartByDefault() =
+        testScope.runTest {
+            starter.start()
+
+            assertThat(fakeStartActivity.startIntents).isEmpty()
+        }
+
+    @Test
+    fun start_onToggle_startsActivity() =
+        testScope.runTest {
+            starter.start()
+
+            testHelper.toggle(deviceId = 456)
+
+            verifyShortcutHelperActivityStarted()
+        }
+
+    @Test
+    fun start_onToggle_multipleTimesStartsActivityOnlyWhenNotStarted() =
+        testScope.runTest {
+            starter.start()
+
+            testHelper.toggle(deviceId = 456)
+            testHelper.toggle(deviceId = 456)
+            testHelper.toggle(deviceId = 456)
+            testHelper.toggle(deviceId = 456)
+
+            verifyShortcutHelperActivityStarted(numTimes = 2)
+        }
+
+    @Test
+    fun start_onRequestShowShortcuts_startsActivity() =
+        testScope.runTest {
+            starter.start()
+
+            testHelper.showFromActivity()
+
+            verifyShortcutHelperActivityStarted()
+        }
+
+    @Test
+    fun start_onRequestShowShortcuts_multipleTimes_startsActivityOnlyOnce() =
+        testScope.runTest {
+            starter.start()
+
+            testHelper.showFromActivity()
+            testHelper.showFromActivity()
+            testHelper.showFromActivity()
+
+            verifyShortcutHelperActivityStarted(numTimes = 1)
+        }
+
+    @Test
+    fun start_onRequestShowShortcuts_multipleTimes_startsActivityOnlyWhenNotStarted() =
+        testScope.runTest {
+            starter.start()
+
+            testHelper.hideFromActivity()
+            testHelper.hideForSystem()
+            testHelper.toggle(deviceId = 987)
+            testHelper.showFromActivity()
+            testHelper.hideFromActivity()
+            testHelper.hideForSystem()
+            testHelper.toggle(deviceId = 456)
+            testHelper.showFromActivity()
+
+            verifyShortcutHelperActivityStarted(numTimes = 2)
+        }
+
+    private fun verifyShortcutHelperActivityStarted(numTimes: Int = 1) {
+        assertThat(fakeStartActivity.startIntents).hasSize(numTimes)
+        fakeStartActivity.startIntents.forEach { intent ->
+            assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+            assertThat(intent.filterEquals(Intent(context, ShortcutHelperActivity::class.java)))
+                .isTrue()
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
new file mode 100644
index 0000000..44a8904
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
@@ -0,0 +1,155 @@
+/*
+ * 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.keyboard.shortcut.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
+import com.android.systemui.keyboard.shortcut.shortcutHelperViewModel
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.model.sysUiState
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class ShortcutHelperViewModelTest : SysuiTestCase() {
+
+    private val kosmos =
+        Kosmos().also {
+            it.testCase = this
+            it.testDispatcher = UnconfinedTestDispatcher()
+        }
+
+    private val testScope = kosmos.testScope
+    private val testHelper = kosmos.shortcutHelperTestHelper
+    private val sysUiState = kosmos.sysUiState
+    private val viewModel = kosmos.shortcutHelperViewModel
+
+    @Test
+    fun shouldShow_falseByDefault() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(viewModel.shouldShow)
+
+            assertThat(shouldShow).isFalse()
+        }
+
+    @Test
+    fun shouldShow_trueAfterShowRequested() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(viewModel.shouldShow)
+
+            testHelper.showFromActivity()
+
+            assertThat(shouldShow).isTrue()
+        }
+
+    @Test
+    fun shouldShow_trueAfterToggleRequested() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(viewModel.shouldShow)
+
+            testHelper.toggle(deviceId = 123)
+
+            assertThat(shouldShow).isTrue()
+        }
+
+    @Test
+    fun shouldShow_falseAfterToggleTwice() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(viewModel.shouldShow)
+
+            testHelper.toggle(deviceId = 123)
+            testHelper.toggle(deviceId = 123)
+
+            assertThat(shouldShow).isFalse()
+        }
+
+    @Test
+    fun shouldShow_falseAfterViewClosed() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(viewModel.shouldShow)
+
+            testHelper.toggle(deviceId = 567)
+            viewModel.onViewClosed()
+
+            assertThat(shouldShow).isFalse()
+        }
+
+    @Test
+    fun shouldShow_doesNotEmitDuplicateValues() =
+        testScope.runTest {
+            val shouldShowValues by collectValues(viewModel.shouldShow)
+
+            testHelper.hideForSystem()
+            testHelper.toggle(deviceId = 987)
+            testHelper.showFromActivity()
+            viewModel.onViewClosed()
+            testHelper.hideFromActivity()
+            testHelper.hideForSystem()
+            testHelper.toggle(deviceId = 456)
+            testHelper.showFromActivity()
+
+            assertThat(shouldShowValues).containsExactly(false, true, false, true).inOrder()
+        }
+
+    @Test
+    fun shouldShow_emitsLatestValueToNewSubscribers() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(viewModel.shouldShow)
+
+            testHelper.showFromActivity()
+
+            val shouldShowNew by collectLastValue(viewModel.shouldShow)
+            assertThat(shouldShowNew).isEqualTo(shouldShow)
+        }
+
+    @Test
+    fun sysUiStateFlag_disabledByDefault() =
+        testScope.runTest {
+            assertThat(sysUiState.isFlagEnabled(SYSUI_STATE_SHORTCUT_HELPER_SHOWING)).isFalse()
+        }
+
+    @Test
+    fun sysUiStateFlag_trueAfterViewOpened() =
+        testScope.runTest {
+            viewModel.onViewOpened()
+
+            assertThat(sysUiState.isFlagEnabled(SYSUI_STATE_SHORTCUT_HELPER_SHOWING)).isTrue()
+        }
+
+    @Test
+    fun sysUiStateFlag_falseAfterViewClosed() =
+        testScope.runTest {
+            viewModel.onViewOpened()
+            viewModel.onViewClosed()
+
+            assertThat(sysUiState.isFlagEnabled(SYSUI_STATE_SHORTCUT_HELPER_SHOWING)).isFalse()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index b50d248..977116e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -8,6 +8,8 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -19,6 +21,10 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.setSceneTransition
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.utils.GlobalWindowManager
@@ -69,12 +75,13 @@
         resourceTrimmer =
             ResourceTrimmer(
                 keyguardInteractor,
-                powerInteractor,
-                kosmos.keyguardTransitionInteractor,
-                globalWindowManager,
-                testScope.backgroundScope,
-                kosmos.testDispatcher,
-                featureFlags
+                powerInteractor = powerInteractor,
+                keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+                globalWindowManager = globalWindowManager,
+                applicationScope = testScope.backgroundScope,
+                bgDispatcher = kosmos.testDispatcher,
+                featureFlags = featureFlags,
+                sceneInteractor = kosmos.sceneInteractor,
             )
         resourceTrimmer.start()
     }
@@ -203,6 +210,7 @@
 
     @Test
     @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
+    @DisableSceneContainer
     fun keyguardTransitionsToGone_trimsFontCache() =
         testScope.runTest {
             keyguardTransitionRepository.sendTransitionSteps(
@@ -218,6 +226,20 @@
 
     @Test
     @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
+    @EnableSceneContainer
+    fun keyguardTransitionsToGone_trimsFontCache_scene_container() =
+        testScope.runTest {
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+            verify(globalWindowManager, times(1))
+                .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
+            verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_FONT)
+            verifyNoMoreInteractions(globalWindowManager)
+        }
+
+    @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
+    @DisableSceneContainer
     fun keyguardTransitionsToGone_flagDisabled_doesNotTrimFontCache() =
         testScope.runTest {
             featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, false)
@@ -231,4 +253,18 @@
                 .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
             verify(globalWindowManager, times(0)).trimCaches(any())
         }
+
+    @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
+    @EnableSceneContainer
+    fun keyguardTransitionsToGone_flagDisabled_doesNotTrimFontCache_scene_container() =
+        testScope.runTest {
+            featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, false)
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+            // Memory hidden should still be called.
+            verify(globalWindowManager, times(1))
+                .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
+            verify(globalWindowManager, times(0)).trimCaches(any())
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 53560d7..48a5df9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -25,16 +25,19 @@
 import androidx.test.filters.SmallTest
 import com.android.app.animation.Interpolators
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.util.KeyguardTransitionRunner
+import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -45,6 +48,8 @@
 import kotlinx.coroutines.flow.dropWhile
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.After
@@ -372,6 +377,43 @@
             assertThat(wtfHandler.failed).isTrue()
         }
 
+    @Test
+    fun simulateRaceConditionIsProcessedInOrder() =
+        testScope.runTest {
+            val ktr = KeyguardTransitionRepositoryImpl(kosmos.testDispatcher)
+            val steps by collectValues(ktr.transitions.dropWhile { step -> step.from == OFF })
+
+            // Add a delay to the first transition in order to attempt to have the second transition
+            // be processed first
+            val info1 = TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator = null)
+            launch {
+                ktr.forceDelayForRaceConditionTest = true
+                ktr.startTransition(info1)
+            }
+            val info2 = TransitionInfo(OWNER_NAME, LOCKSCREEN, OCCLUDED, animator = null)
+            launch {
+                ktr.forceDelayForRaceConditionTest = false
+                ktr.startTransition(info2)
+            }
+
+            runCurrent()
+            assertThat(steps.isEmpty()).isTrue()
+
+            advanceTimeBy(60L)
+            assertThat(steps[0])
+                .isEqualTo(
+                    TransitionStep(info1.from, info1.to, 0f, TransitionState.STARTED, OWNER_NAME)
+                )
+            assertThat(steps[1])
+                .isEqualTo(
+                    TransitionStep(info1.from, info1.to, 0f, TransitionState.CANCELED, OWNER_NAME)
+                )
+            assertThat(steps[2])
+                .isEqualTo(
+                    TransitionStep(info2.from, info2.to, 0f, TransitionState.STARTED, OWNER_NAME)
+                )
+        }
+
     private fun listWithStep(
         step: BigDecimal,
         start: BigDecimal = BigDecimal.ZERO,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
index ac5823e..0bdf47a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
+import com.android.systemui.keyguard.data.repository.keyguardBlueprintRepository
 import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
 import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
 import com.android.systemui.kosmos.testScope
@@ -40,6 +41,7 @@
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -54,7 +56,7 @@
 class KeyguardBlueprintInteractorTest : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val underTest by lazy { kosmos.keyguardBlueprintInteractor }
+    private val underTest = kosmos.keyguardBlueprintInteractor
     private val clockRepository by lazy { kosmos.fakeKeyguardClockRepository }
     private val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
     private val fingerprintPropertyRepository by lazy { kosmos.fakeFingerprintPropertyRepository }
@@ -79,8 +81,9 @@
             val blueprintId by collectLastValue(underTest.blueprintId)
             kosmos.shadeRepository.setShadeMode(ShadeMode.Single)
             configurationRepository.onConfigurationChange()
-            runCurrent()
 
+            runCurrent()
+            advanceUntilIdle()
             assertThat(blueprintId).isEqualTo(DefaultKeyguardBlueprint.Companion.DEFAULT)
         }
     }
@@ -92,8 +95,9 @@
             val blueprintId by collectLastValue(underTest.blueprintId)
             kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
             configurationRepository.onConfigurationChange()
-            runCurrent()
 
+            runCurrent()
+            advanceUntilIdle()
             assertThat(blueprintId).isEqualTo(SplitShadeKeyguardBlueprint.Companion.ID)
         }
     }
@@ -102,12 +106,13 @@
     @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
     fun fingerprintPropertyInitialized_updatesBlueprint() {
         testScope.runTest {
-            val blueprintId by collectLastValue(underTest.blueprintId)
-            kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
-            fingerprintPropertyRepository.supportsUdfps() // initialize properties
-            runCurrent()
+            assertThat(kosmos.keyguardBlueprintRepository.targetTransitionConfig).isNull()
 
-            assertThat(blueprintId).isEqualTo(SplitShadeKeyguardBlueprint.Companion.ID)
+            fingerprintPropertyRepository.supportsUdfps() // initialize properties
+
+            runCurrent()
+            advanceUntilIdle()
+            assertThat(kosmos.keyguardBlueprintRepository.targetTransitionConfig).isNotNull()
         }
     }
 
@@ -119,9 +124,23 @@
             kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
             clockRepository.setCurrentClock(clockController)
             configurationRepository.onConfigurationChange()
-            runCurrent()
 
+            runCurrent()
+            advanceUntilIdle()
             assertThat(blueprintId).isEqualTo(DefaultKeyguardBlueprint.DEFAULT)
         }
     }
+
+    @Test
+    fun testRefreshFromConfigChange() {
+        testScope.runTest {
+            assertThat(kosmos.keyguardBlueprintRepository.targetTransitionConfig).isNull()
+
+            configurationRepository.onConfigurationChange()
+
+            runCurrent()
+            advanceUntilIdle()
+            assertThat(kosmos.keyguardBlueprintRepository.targetTransitionConfig).isNotNull()
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index 62855d7..974e3bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -21,12 +21,18 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.DismissAction
 import com.android.systemui.keyguard.shared.model.KeyguardDone
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.setSceneTransition
+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
@@ -65,10 +71,11 @@
 
         underTest =
             KeyguardDismissActionInteractor(
-                keyguardRepository,
-                kosmos.keyguardTransitionInteractor,
-                dismissInteractorWithDependencies.interactor,
-                testScope.backgroundScope,
+                repository = keyguardRepository,
+                transitionInteractor = kosmos.keyguardTransitionInteractor,
+                dismissInteractor = dismissInteractorWithDependencies.interactor,
+                applicationScope = testScope.backgroundScope,
+                sceneInteractor = kosmos.sceneInteractor,
             )
     }
 
@@ -153,6 +160,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun executeDismissAction_dismissKeyguardRequestWithoutImmediateDismissAction() =
         testScope.runTest {
             val executeDismissAction by collectLastValue(underTest.executeDismissAction)
@@ -179,6 +187,29 @@
         }
 
     @Test
+    @EnableSceneContainer
+    fun executeDismissAction_dismissKeyguardRequestWithoutImmediateDismissAction_scene_container() =
+        testScope.runTest {
+            val executeDismissAction by collectLastValue(underTest.executeDismissAction)
+
+            // WHEN a keyguard action will run after the keyguard is gone
+            val onDismissAction = {}
+            keyguardRepository.setDismissAction(
+                DismissAction.RunAfterKeyguardGone(
+                    dismissAction = onDismissAction,
+                    onCancelAction = {},
+                    message = "message",
+                    willAnimateOnLockscreen = true,
+                )
+            )
+            assertThat(executeDismissAction).isNull()
+
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+            assertThat(executeDismissAction).isNotNull()
+        }
+
+    @Test
     fun resetDismissAction() =
         testScope.runTest {
             val resetDismissAction by collectLastValue(underTest.resetDismissAction)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
index 9ccf212..f32e775 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
@@ -274,4 +274,27 @@
             runCurrent()
             assertThat(isAnimatingSurface).isFalse()
         }
+
+    @Test
+    fun notificationLaunchFalse_isAnimatingSurfaceFalse() =
+        testScope.runTest {
+            val isAnimatingSurface by collectLastValue(underTest.isAnimatingSurface)
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                    transitionState = TransitionState.STARTED,
+                )
+            )
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                    transitionState = TransitionState.FINISHED,
+                )
+            )
+            kosmos.notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(false)
+            runCurrent()
+            assertThat(isAnimatingSurface).isFalse()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 1dc58d1..fa3fe5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.app.StatusBarManager
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.keyguard.KeyguardSecurityModel
@@ -29,7 +31,10 @@
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.dock.fakeDockManager
+import com.android.systemui.flags.BrokenWithSceneContainer
+import com.android.systemui.flags.DisableSceneContainer
 import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeCommandQueue
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -46,7 +51,8 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
-import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.statusbar.commandQueue
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
@@ -61,13 +67,14 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.spy
 import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 /**
  * Class for testing user journeys through the interactors. They will all be activated during setup,
@@ -75,8 +82,8 @@
  */
 @ExperimentalCoroutinesApi
 @SmallTest
-@RunWith(JUnit4::class)
-class KeyguardTransitionScenariosTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
             fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
@@ -84,33 +91,52 @@
         }
     private val testScope = kosmos.testScope
 
-    private val keyguardRepository = kosmos.fakeKeyguardRepository
-    private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
+    private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+    private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
     private var commandQueue = kosmos.fakeCommandQueue
-    private val shadeRepository = kosmos.fakeShadeRepository
-    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+    private val transitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
     private lateinit var featureFlags: FakeFeatureFlags
 
     // Used to verify transition requests for test output
     @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
 
-    private val fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor
-    private val fromDreamingTransitionInteractor = kosmos.fromDreamingTransitionInteractor
-    private val fromDozingTransitionInteractor = kosmos.fromDozingTransitionInteractor
-    private val fromOccludedTransitionInteractor = kosmos.fromOccludedTransitionInteractor
-    private val fromGoneTransitionInteractor = kosmos.fromGoneTransitionInteractor
-    private val fromAodTransitionInteractor = kosmos.fromAodTransitionInteractor
-    private val fromAlternateBouncerTransitionInteractor =
+    private val fromLockscreenTransitionInteractor by lazy {
+        kosmos.fromLockscreenTransitionInteractor
+    }
+    private val fromDreamingTransitionInteractor by lazy { kosmos.fromDreamingTransitionInteractor }
+    private val fromDozingTransitionInteractor by lazy { kosmos.fromDozingTransitionInteractor }
+    private val fromOccludedTransitionInteractor by lazy { kosmos.fromOccludedTransitionInteractor }
+    private val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor }
+    private val fromAodTransitionInteractor by lazy { kosmos.fromAodTransitionInteractor }
+    private val fromAlternateBouncerTransitionInteractor by lazy {
         kosmos.fromAlternateBouncerTransitionInteractor
-    private val fromPrimaryBouncerTransitionInteractor =
+    }
+    private val fromPrimaryBouncerTransitionInteractor by lazy {
         kosmos.fromPrimaryBouncerTransitionInteractor
-    private val fromDreamingLockscreenHostedTransitionInteractor =
+    }
+    private val fromDreamingLockscreenHostedTransitionInteractor by lazy {
         kosmos.fromDreamingLockscreenHostedTransitionInteractor
-    private val fromGlanceableHubTransitionInteractor = kosmos.fromGlanceableHubTransitionInteractor
+    }
+    private val fromGlanceableHubTransitionInteractor by lazy {
+        kosmos.fromGlanceableHubTransitionInteractor
+    }
 
-    private val powerInteractor = kosmos.powerInteractor
-    private val communalInteractor = kosmos.communalInteractor
-    private val dockManager = kosmos.fakeDockManager
+    private val powerInteractor by lazy { kosmos.powerInteractor }
+    private val communalInteractor by lazy { kosmos.communalInteractor }
+    private val dockManager by lazy { kosmos.fakeDockManager }
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags!!)
+    }
 
     @Before
     fun setUp() {
@@ -119,9 +145,11 @@
         whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
 
         mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
-        mSetFlagsRule.disableFlags(
-            Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
-        )
+        if (!SceneContainerFlag.isEnabled) {
+            mSetFlagsRule.disableFlags(
+                Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+            )
+        }
         featureFlags = FakeFeatureFlags()
 
         fromLockscreenTransitionInteractor.start()
@@ -137,6 +165,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     fun lockscreenToPrimaryBouncerViaBouncerShowingCall() =
         testScope.runTest {
             // GIVEN a prior transition has run to LOCKSCREEN
@@ -210,6 +239,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun lockscreenToDreaming() =
         testScope.runTest {
             // GIVEN a device that is not dreaming or dozing
@@ -238,6 +268,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun lockscreenToDreamingLockscreenHosted() =
         testScope.runTest {
             // GIVEN a device that is not dreaming or dozing
@@ -348,6 +379,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun dreamingLockscreenHostedToGone() =
         testScope.runTest {
             // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
@@ -374,6 +406,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun dreamingLockscreenHostedToPrimaryBouncer() =
         testScope.runTest {
             // GIVEN a device dreaming with lockscreen hosted dream and not dozing
@@ -527,6 +560,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun dozingToGoneWithUnlock() =
         testScope.runTest {
             // GIVEN a prior transition has run to DOZING
@@ -600,12 +634,13 @@
 
     /** This handles security method NONE and screen off with lock timeout */
     @Test
+    @DisableSceneContainer
     fun dreamingToGoneWithKeyguardNotShowing() =
         testScope.runTest {
             // GIVEN a prior transition has run to DREAMING
             keyguardRepository.setDreamingWithOverlay(true)
             runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
-            runCurrent()
+            advanceTimeBy(60L)
 
             // WHEN the device wakes up without a keyguard
             keyguardRepository.setKeyguardShowing(false)
@@ -656,6 +691,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun goneToDozing() =
         testScope.runTest {
             // GIVEN a device with AOD not available
@@ -681,6 +717,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun goneToAod() =
         testScope.runTest {
             // GIVEN a device with AOD available
@@ -706,6 +743,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun goneToLockscreen() =
         testScope.runTest {
             // GIVEN a prior transition has run to GONE
@@ -727,6 +765,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun goneToDreaming() =
         testScope.runTest {
             // GIVEN a device that is not dreaming or dozing
@@ -755,6 +794,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun goneToGlanceableHub() =
         testScope.runTest {
             // GIVEN a prior transition has run to GONE
@@ -784,6 +824,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun alternateBouncerToPrimaryBouncer() =
         testScope.runTest {
             // GIVEN a prior transition has run to ALTERNATE_BOUNCER
@@ -897,6 +938,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun alternateBouncerToGone() =
         testScope.runTest {
             // GIVEN a prior transition has run to ALTERNATE_BOUNCER
@@ -959,6 +1001,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun primaryBouncerToAod() =
         testScope.runTest {
             // GIVEN aod available
@@ -989,6 +1032,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun primaryBouncerToDozing() =
         testScope.runTest {
             // GIVEN a prior transition has run to PRIMARY_BOUNCER
@@ -1017,6 +1061,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun primaryBouncerToLockscreen() =
         testScope.runTest {
             // GIVEN a prior transition has run to PRIMARY_BOUNCER
@@ -1040,6 +1085,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun primaryBouncerToGlanceableHub() =
         testScope.runTest {
             // GIVEN a prior transition has run to PRIMARY_BOUNCER
@@ -1071,6 +1117,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun primaryBouncerToGlanceableHubWhileDreaming() =
         testScope.runTest {
             // GIVEN a prior transition has run to PRIMARY_BOUNCER
@@ -1106,6 +1153,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun primaryBouncerToDreamingLockscreenHosted() =
         testScope.runTest {
             // GIVEN device dreaming with the lockscreen hosted dream and not dozing
@@ -1135,6 +1183,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun occludedToGone() =
         testScope.runTest {
             // GIVEN a device on lockscreen
@@ -1165,6 +1214,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun occludedToLockscreen() =
         testScope.runTest {
             // GIVEN a device on lockscreen
@@ -1193,6 +1243,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun occludedToGlanceableHub() =
         testScope.runTest {
             // GIVEN a device on lockscreen
@@ -1229,6 +1280,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun occludedToGlanceableHubWhenInitiallyOnHub() =
         testScope.runTest {
             // GIVEN a device on lockscreen and communal is available
@@ -1314,6 +1366,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun primaryBouncerToOccluded() =
         testScope.runTest {
             // GIVEN a prior transition has run to PRIMARY_BOUNCER
@@ -1339,6 +1392,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun dozingToOccluded() =
         testScope.runTest {
             // GIVEN a prior transition has run to DOZING
@@ -1364,6 +1418,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun dreamingToOccluded() =
         testScope.runTest {
             // GIVEN a prior transition has run to DREAMING
@@ -1392,6 +1447,39 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
+    @EnableFlags(Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE)
+    fun dreamingToOccludedToDreaming() =
+        testScope.runTest {
+            // GIVEN a device on lockscreen
+            keyguardRepository.setKeyguardShowing(true)
+            runCurrent()
+
+            // Given a device that is dreaming
+            keyguardRepository.setDreaming(true)
+
+            // GIVEN a prior transition has run to OCCLUDED
+            runTransitionAndSetWakefulness(KeyguardState.DREAMING, KeyguardState.OCCLUDED)
+            keyguardRepository.setKeyguardOccluded(true)
+            runCurrent()
+
+            // WHEN occlusion ends
+            keyguardRepository.setKeyguardOccluded(false)
+            runCurrent()
+
+            // THEN a transition to DREAMING should occur
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromOccludedTransitionInteractor::class.simpleName,
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.DREAMING,
+                    animatorAssertion = { it.isNotNull() },
+                )
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun dreamingToPrimaryBouncer() =
         testScope.runTest {
             // GIVEN a prior transition has run to DREAMING
@@ -1445,6 +1533,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun dreamingToGlanceableHub() =
         testScope.runTest {
             // GIVEN a prior transition has run to DREAMING
@@ -1484,6 +1573,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun lockscreenToOccluded() =
         testScope.runTest {
             // GIVEN a prior transition has run to LOCKSCREEN
@@ -1507,6 +1597,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun aodToOccluded() =
         testScope.runTest {
             // GIVEN a prior transition has run to AOD
@@ -1530,6 +1621,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun aodToPrimaryBouncer() =
         testScope.runTest {
             // GIVEN a prior transition has run to AOD
@@ -1553,6 +1645,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun lockscreenToOccluded_fromCameraGesture() =
         testScope.runTest {
             // GIVEN a prior transition has run to LOCKSCREEN
@@ -1576,7 +1669,9 @@
             // THEN a transition from DOZING => OCCLUDED should occur
             assertThat(transitionRepository)
                 .startedTransition(
-                    ownerName = "FromDozingTransitionInteractor",
+                    ownerName =
+                        "FromDozingTransitionInteractor" +
+                            "(keyguardInteractor.onCameraLaunchDetected)",
                     from = KeyguardState.DOZING,
                     to = KeyguardState.OCCLUDED,
                     animatorAssertion = { it.isNotNull() },
@@ -1586,6 +1681,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun lockscreenToPrimaryBouncerDragging() =
         testScope.runTest {
             // GIVEN a prior transition has run to LOCKSCREEN
@@ -1595,8 +1691,8 @@
             // GIVEN the keyguard is showing locked
             keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
             runCurrent()
-            shadeRepository.setLegacyShadeTracking(true)
-            shadeRepository.setLegacyShadeExpansion(.9f)
+            shadeTestUtil.setTracking(true)
+            shadeTestUtil.setShadeExpansion(.9f)
             runCurrent()
 
             // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
@@ -1613,8 +1709,8 @@
             // WHEN the user stops dragging and shade is back to expanded
             clearInvocations(transitionRepository)
             runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER)
-            shadeRepository.setLegacyShadeTracking(false)
-            shadeRepository.setLegacyShadeExpansion(1f)
+            shadeTestUtil.setTracking(false)
+            shadeTestUtil.setShadeExpansion(1f)
             runCurrent()
 
             // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
@@ -1629,6 +1725,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun lockscreenToGlanceableHub() =
         testScope.runTest {
             // GIVEN a prior transition has run to LOCKSCREEN
@@ -1686,6 +1783,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun glanceableHubToLockscreen() =
         testScope.runTest {
             // GIVEN a prior transition has run to GLANCEABLE_HUB
@@ -1740,6 +1838,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun glanceableHubToDozing() =
         testScope.runTest {
             // GIVEN a prior transition has run to GLANCEABLE_HUB
@@ -1761,6 +1860,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun glanceableHubToPrimaryBouncer() =
         testScope.runTest {
             // GIVEN a prior transition has run to ALTERNATE_BOUNCER
@@ -1782,6 +1882,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun glanceableHubToAlternateBouncer() =
         testScope.runTest {
             // GIVEN a prior transition has run to ALTERNATE_BOUNCER
@@ -1803,6 +1904,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun glanceableHubToOccluded() =
         testScope.runTest {
             // GIVEN a prior transition has run to GLANCEABLE_HUB
@@ -1833,6 +1935,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun glanceableHubToGone() =
         testScope.runTest {
             // GIVEN a prior transition has run to GLANCEABLE_HUB
@@ -1854,6 +1957,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun glanceableHubToDreaming() =
         testScope.runTest {
             // GIVEN that we are dreaming and not dozing
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
index b1a8dd1..a77169e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -18,20 +18,29 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertEquals
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -57,14 +66,22 @@
                 .thenReturn(surfaceBehindIsAnimatingFlow)
         }
 
-    private val underTest = kosmos.windowManagerLockscreenVisibilityInteractor
+    private val underTest = lazy { kosmos.windowManagerLockscreenVisibilityInteractor }
     private val testScope = kosmos.testScope
     private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
 
+    @Before
+    fun setUp() {
+        // lazy value needs to be called here otherwise flow collection misbehaves
+        underTest.value
+        kosmos.sceneContainerRepository.setTransitionState(sceneTransitions)
+    }
+
     @Test
+    @DisableSceneContainer
     fun surfaceBehindVisibility_switchesToCorrectFlow() =
         testScope.runTest {
-            val values by collectValues(underTest.surfaceBehindVisibility)
+            val values by collectValues(underTest.value.surfaceBehindVisibility)
 
             // Start on LOCKSCREEN.
             transitionRepository.sendTransitionStep(
@@ -170,9 +187,10 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun testUsingGoingAwayAnimation_duringTransitionToGone() =
         testScope.runTest {
-            val values by collectValues(underTest.usingKeyguardGoingAwayAnimation)
+            val values by collectValues(underTest.value.usingKeyguardGoingAwayAnimation)
 
             // Start on LOCKSCREEN.
             transitionRepository.sendTransitionStep(
@@ -230,9 +248,10 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun testNotUsingGoingAwayAnimation_evenWhenAnimating_ifStateIsNotGone() =
         testScope.runTest {
-            val values by collectValues(underTest.usingKeyguardGoingAwayAnimation)
+            val values by collectValues(underTest.value.usingKeyguardGoingAwayAnimation)
 
             // Start on LOCKSCREEN.
             transitionRepository.sendTransitionStep(
@@ -319,9 +338,10 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun lockscreenVisibility_visibleWhenGone() =
         testScope.runTest {
-            val values by collectValues(underTest.lockscreenVisibility)
+            val values by collectValues(underTest.value.lockscreenVisibility)
 
             // Start on LOCKSCREEN.
             transitionRepository.sendTransitionStep(
@@ -385,9 +405,10 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun testLockscreenVisibility_usesFromState_ifCanceled() =
         testScope.runTest {
-            val values by collectValues(underTest.lockscreenVisibility)
+            val values by collectValues(underTest.value.lockscreenVisibility)
 
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
@@ -486,9 +507,10 @@
      * state during the AOD/isAsleep -> GONE transition is AOD (where lockscreen visibility = true).
      */
     @Test
+    @DisableSceneContainer
     fun testLockscreenVisibility_falseDuringTransitionToGone_fromCanceledGone() =
         testScope.runTest {
-            val values by collectValues(underTest.lockscreenVisibility)
+            val values by collectValues(underTest.value.lockscreenVisibility)
 
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
@@ -587,11 +609,11 @@
             )
         }
 
-    /**  */
     @Test
+    @DisableSceneContainer
     fun testLockscreenVisibility_trueDuringTransitionToGone_fromNotCanceledGone() =
         testScope.runTest {
-            val values by collectValues(underTest.lockscreenVisibility)
+            val values by collectValues(underTest.value.lockscreenVisibility)
 
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
@@ -702,4 +724,109 @@
                 values
             )
         }
+
+    @Test
+    @EnableSceneContainer
+    fun sceneContainer_lockscreenVisibility_visibleWhenNotGone() =
+        testScope.runTest {
+            val lockscreenVisibility by collectLastValue(underTest.value.lockscreenVisibility)
+
+            sceneTransitions.value = lsToGone
+            assertThat(lockscreenVisibility).isTrue()
+
+            sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone)
+            assertThat(lockscreenVisibility).isFalse()
+
+            sceneTransitions.value = goneToLs
+            assertThat(lockscreenVisibility).isFalse()
+
+            sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
+            assertThat(lockscreenVisibility).isTrue()
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun sceneContainer_lockscreenVisibility_notVisibleWhenReturningToGone() =
+        testScope.runTest {
+            val lockscreenVisibility by collectLastValue(underTest.value.lockscreenVisibility)
+
+            sceneTransitions.value = goneToLs
+            assertThat(lockscreenVisibility).isFalse()
+
+            sceneTransitions.value = lsToGone
+            assertThat(lockscreenVisibility).isFalse()
+
+            sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone)
+            assertThat(lockscreenVisibility).isFalse()
+
+            sceneTransitions.value = goneToLs
+            assertThat(lockscreenVisibility).isFalse()
+
+            sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
+            assertThat(lockscreenVisibility).isTrue()
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun sceneContainer_usingGoingAwayAnimation_duringTransitionToGone() =
+        testScope.runTest {
+            val usingKeyguardGoingAwayAnimation by
+                collectLastValue(underTest.value.usingKeyguardGoingAwayAnimation)
+
+            sceneTransitions.value = lsToGone
+            assertThat(usingKeyguardGoingAwayAnimation).isTrue()
+
+            sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone)
+            assertThat(usingKeyguardGoingAwayAnimation).isFalse()
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun sceneContainer_usingGoingAwayAnimation_surfaceBehindIsAnimating() =
+        testScope.runTest {
+            val usingKeyguardGoingAwayAnimation by
+                collectLastValue(underTest.value.usingKeyguardGoingAwayAnimation)
+
+            sceneTransitions.value = lsToGone
+            surfaceBehindIsAnimatingFlow.emit(true)
+            assertThat(usingKeyguardGoingAwayAnimation).isTrue()
+
+            sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone)
+            assertThat(usingKeyguardGoingAwayAnimation).isTrue()
+
+            sceneTransitions.value = goneToLs
+            assertThat(usingKeyguardGoingAwayAnimation).isTrue()
+
+            surfaceBehindIsAnimatingFlow.emit(false)
+            assertThat(usingKeyguardGoingAwayAnimation).isFalse()
+        }
+
+    companion object {
+        private val progress = MutableStateFlow(0f)
+
+        private val sceneTransitions =
+            MutableStateFlow<ObservableTransitionState>(
+                ObservableTransitionState.Idle(Scenes.Lockscreen)
+            )
+
+        private val lsToGone =
+            ObservableTransitionState.Transition(
+                Scenes.Lockscreen,
+                Scenes.Gone,
+                flowOf(Scenes.Lockscreen),
+                progress,
+                false,
+                flowOf(false)
+            )
+
+        private val goneToLs =
+            ObservableTransitionState.Transition(
+                Scenes.Gone,
+                Scenes.Lockscreen,
+                flowOf(Scenes.Lockscreen),
+                progress,
+                false,
+                flowOf(false)
+            )
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
new file mode 100644
index 0000000..8a5af09
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
@@ -0,0 +1,1318 @@
+/*
+ * 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.domain.interactor.scenetransition
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.realKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.keyguard.shared.model.TransitionState
+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.Scenes
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
+    private val kosmos =
+        testKosmos().apply { keyguardTransitionRepository = realKeyguardTransitionRepository }
+
+    private val testScope = kosmos.testScope
+    private val underTest = kosmos.lockscreenSceneTransitionInteractor
+
+    private val progress = MutableStateFlow(0f)
+
+    private val sceneTransitions =
+        MutableStateFlow<ObservableTransitionState>(
+            ObservableTransitionState.Idle(Scenes.Lockscreen)
+        )
+
+    private val lsToGone =
+        ObservableTransitionState.Transition(
+            Scenes.Lockscreen,
+            Scenes.Gone,
+            flowOf(Scenes.Lockscreen),
+            progress,
+            false,
+            flowOf(false)
+        )
+
+    private val goneToLs =
+        ObservableTransitionState.Transition(
+            Scenes.Gone,
+            Scenes.Lockscreen,
+            flowOf(Scenes.Lockscreen),
+            progress,
+            false,
+            flowOf(false)
+        )
+
+    @Before
+    fun setUp() {
+        underTest.start()
+        kosmos.sceneContainerRepository.setTransitionState(sceneTransitions)
+        testScope.launch {
+            kosmos.realKeyguardTransitionRepository.emitInitialStepsFromOff(
+                KeyguardState.LOCKSCREEN
+            )
+        }
+    }
+
+    /** STL: Ls -> Gone, then settle with Idle(Gone). This is the default case. */
+    @Test
+    fun transition_from_ls_scene_end_in_gone() =
+        testScope.runTest {
+            sceneTransitions.value = lsToGone
+
+            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.Gone)
+            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
+     * finish/settle to progress 0.0f.
+     */
+    @Test
+    fun transition_from_ls_scene_end_in_ls() =
+        testScope.runTest {
+            sceneTransitions.value = lsToGone
+
+            val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+            val allSteps by collectValues(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)
+
+            assertTransition(
+                step = allSteps[allSteps.size - 3],
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.CANCELED,
+                progress = 0.4f,
+            )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 2],
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.STARTED,
+                progress = 0.6f,
+            )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 1],
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.FINISHED,
+                progress = 1f,
+            )
+        }
+
+    /**
+     * STL: Ls -> Gone, then settle with Idle(Ls). KTF starts in AOD and needs to inverse correctly
+     * back to AOD.
+     */
+    @Test
+    fun transition_from_ls_scene_on_aod_end_in_ls() =
+        testScope.runTest {
+            val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+            val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+
+            kosmos.realKeyguardTransitionRepository.startTransition(
+                TransitionInfo(
+                    ownerName = this.javaClass.simpleName,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    animator = null,
+                    modeOnCanceled = TransitionModeOnCanceled.RESET
+                )
+            )
+            sceneTransitions.value = lsToGone
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.AOD,
+                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)
+
+            assertTransition(
+                step = allSteps[allSteps.size - 3],
+                from = KeyguardState.AOD,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.CANCELED,
+                progress = 0.4f,
+            )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 2],
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.AOD,
+                state = TransitionState.STARTED,
+                progress = 0.6f,
+            )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 1],
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.AOD,
+                state = TransitionState.FINISHED,
+                progress = 1f,
+            )
+        }
+
+    /**
+     * STL: Gone -> Ls, then settle with Idle(Ls). This is the default case in the reverse
+     * direction.
+     */
+    @Test
+    fun transition_to_ls_scene_end_in_ls() =
+        testScope.runTest {
+            val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+            sceneTransitions.value = goneToLs
+
+            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() =
+        testScope.runTest {
+            underTest.onSceneAboutToChange(Scenes.Lockscreen, KeyguardState.AOD)
+            sceneTransitions.value = goneToLs
+
+            val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.AOD,
+                state = TransitionState.RUNNING,
+                progress = 0f,
+            )
+
+            sceneTransitions.value =
+                ObservableTransitionState.Transition(
+                    Scenes.Shade,
+                    Scenes.Lockscreen,
+                    flowOf(Scenes.Lockscreen),
+                    progress,
+                    false,
+                    flowOf(false)
+                )
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.RUNNING,
+                progress = 0f,
+            )
+        }
+
+    /**
+     * STL: Gone -> Ls, then settle with Idle(Gone). KTF in this scenario needs to invert the
+     * transition UNDEFINED -> LS to LS -> UNDEFINED as there is no mechanism in KTF to
+     * finish/settle to progress 0.0f.
+     */
+    @Test
+    fun transition_to_ls_scene_end_in_from_scene() =
+        testScope.runTest {
+            sceneTransitions.value = goneToLs
+
+            val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+            val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+            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.Gone)
+            val stepM3 = allSteps[allSteps.size - 3]
+            val stepM2 = allSteps[allSteps.size - 2]
+
+            assertTransition(
+                step = stepM3,
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.CANCELED,
+                progress = 0.4f,
+            )
+
+            assertTransition(
+                step = stepM2,
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.STARTED,
+                progress = 0.6f,
+            )
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.FINISHED,
+                progress = 1f,
+            )
+        }
+
+    /**
+     * STL: Gone -> Ls, then interrupted by Shade -> Ls. KTF in this scenario needs to invert the
+     * transition UNDEFINED -> LS to LS -> UNDEFINED as there is no mechanism in KTF to
+     * finish/settle to progress 0.0f. Then restart a different transition UNDEFINED -> Ls.
+     */
+    @Test
+    fun transition_to_ls_scene_end_in_to_ls_transition() =
+        testScope.runTest {
+            val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+            val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+            sceneTransitions.value = goneToLs
+            progress.value = 0.4f
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.RUNNING,
+                progress = 0.4f,
+            )
+
+            sceneTransitions.value =
+                ObservableTransitionState.Transition(
+                    Scenes.Shade,
+                    Scenes.Lockscreen,
+                    flowOf(Scenes.Lockscreen),
+                    progress,
+                    false,
+                    flowOf(false)
+                )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 5],
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.CANCELED,
+                progress = 0.4f,
+            )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 4],
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.STARTED,
+                progress = 0.6f,
+            )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 3],
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.FINISHED,
+                progress = 1f,
+            )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 2],
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.STARTED,
+                progress = 0f,
+            )
+
+            progress.value = 0.2f
+            assertTransition(
+                step = allSteps[allSteps.size - 1],
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.RUNNING,
+                progress = 0.2f,
+            )
+        }
+
+    /**
+     * STL: Gone -> Ls, then interrupted by Ls -> Shade. This is like continuing the transition from
+     * Ls before the transition before has properly settled. This can happen in STL e.g. with an
+     * accelerated swipe (quick successive fling gestures).
+     */
+    @Test
+    fun transition_to_ls_scene_end_in_from_ls_transition() =
+        testScope.runTest {
+            val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+            val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+            sceneTransitions.value = goneToLs
+            progress.value = 0.4f
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.RUNNING,
+                progress = 0.4f,
+            )
+
+            sceneTransitions.value =
+                ObservableTransitionState.Transition(
+                    Scenes.Lockscreen,
+                    Scenes.Shade,
+                    flowOf(Scenes.Lockscreen),
+                    progress,
+                    false,
+                    flowOf(false)
+                )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 3],
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.CANCELED,
+                progress = 0.4f,
+            )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 2],
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.STARTED,
+                progress = 0.0f,
+            )
+
+            progress.value = 0.2f
+            assertTransition(
+                step = allSteps[allSteps.size - 1],
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.RUNNING,
+                progress = 0.2f,
+            )
+        }
+
+    /**
+     * STL: Gone -> Ls, then interrupted by Gone -> Shade. This is going back to Gone but starting a
+     * transition from Gone before settling in Gone. KTF needs to make sure the transition is
+     * properly inversed and settled in UNDEFINED.
+     */
+    @Test
+    fun transition_to_ls_scene_end_in_other_transition() =
+        testScope.runTest {
+            val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+            val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+            sceneTransitions.value = goneToLs
+            progress.value = 0.4f
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.RUNNING,
+                progress = 0.4f,
+            )
+
+            sceneTransitions.value =
+                ObservableTransitionState.Transition(
+                    Scenes.Gone,
+                    Scenes.Shade,
+                    flowOf(Scenes.Lockscreen),
+                    progress,
+                    false,
+                    flowOf(false)
+                )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 3],
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.CANCELED,
+                progress = 0.4f,
+            )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 2],
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.STARTED,
+                progress = 0.6f,
+            )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 1],
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.FINISHED,
+                progress = 1f,
+            )
+        }
+
+    /**
+     * STL: Gone -> Ls, then interrupt in KTF LS -> AOD, then stl still finishes in Ls. After a KTF
+     * transition is started (UNDEFINED -> LOCKSCREEN) KTF immediately considers the active scene to
+     * be LOCKSCREEN. This means that all listeners for LOCKSCREEN are active and may start a new
+     * transition LOCKSCREEN -> *. Here we test LS -> AOD.
+     *
+     * KTF is allowed to already start and play the other transition, while the STL transition may
+     * finish later (gesture completes much later). When we eventually settle the STL transition in
+     * Ls we do not want to force KTF back to its original destination (LOCKSCREEN). Instead, for
+     * this scenario the settle can be ignored.
+     */
+    @Test
+    fun transition_to_ls_scene_interrupted_by_ktf_transition_then_finish_in_lockscreen() =
+        testScope.runTest {
+            sceneTransitions.value = goneToLs
+
+            val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+            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,
+            )
+
+            kosmos.realKeyguardTransitionRepository.startTransition(
+                TransitionInfo(
+                    ownerName = this.javaClass.simpleName,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    animator = null,
+                    modeOnCanceled = TransitionModeOnCanceled.RESET
+                )
+            )
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                state = TransitionState.STARTED,
+                progress = 0f,
+            )
+
+            // Scene progress should not affect KTF transition anymore
+            progress.value = 0.7f
+            assertTransition(currentStep!!, progress = 0f)
+
+            // Scene transition still finishes but should not impact KTF transition
+            sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                state = TransitionState.STARTED,
+                progress = 0f,
+            )
+        }
+
+    /**
+     * STL: Gone -> Ls, then interrupt in KTF LS -> AOD, then stl finishes in Gone.
+     *
+     * Refers to: `transition_to_ls_scene_interrupted_by_ktf_transition_then_finish_in_lockscreen`
+     *
+     * This is similar to the previous scenario but the gesture may have gone back to its origin. In
+     * this case we can not ignore the settlement, because whatever KTF has done in the meantime it
+     * needs to immediately finish in UNDEFINED (there is a jump cut).
+     */
+    @Test
+    fun transition_to_ls_scene_interrupted_by_ktf_transition_then_finish_in_gone() =
+        testScope.runTest {
+            sceneTransitions.value = goneToLs
+
+            val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+            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,
+            )
+
+            kosmos.realKeyguardTransitionRepository.startTransition(
+                TransitionInfo(
+                    ownerName = this.javaClass.simpleName,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    animator = null,
+                    modeOnCanceled = TransitionModeOnCanceled.RESET
+                )
+            )
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                state = TransitionState.STARTED,
+                progress = 0f,
+            )
+
+            progress.value = 0.7f
+            assertThat(currentStep?.value).isEqualTo(0f)
+
+            sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone)
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.AOD,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.FINISHED,
+                progress = 1f,
+            )
+        }
+
+    /**
+     * STL: Gone -> Ls, then interrupt in KTF LS -> AOD, then STL Gone -> Shade
+     *
+     * Refers to: `transition_to_ls_scene_interrupted_by_ktf_transition_then_finish_in_lockscreen`
+     *
+     * This is similar to the previous scenario but the gesture may have been interrupted by any
+     * other transition. KTF needs to immediately finish in UNDEFINED (there is a jump cut).
+     */
+    @Test
+    fun transition_to_ls_interrupted_by_ktf_transition_then_interrupted_by_other_transition() =
+        testScope.runTest {
+            sceneTransitions.value = goneToLs
+
+            val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+            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,
+            )
+
+            kosmos.realKeyguardTransitionRepository.startTransition(
+                TransitionInfo(
+                    ownerName = this.javaClass.simpleName,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    animator = null,
+                    modeOnCanceled = TransitionModeOnCanceled.RESET
+                )
+            )
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                state = TransitionState.STARTED,
+                progress = 0f,
+            )
+
+            progress.value = 0.7f
+            assertTransition(currentStep!!, progress = 0f)
+
+            sceneTransitions.value =
+                ObservableTransitionState.Transition(
+                    Scenes.Gone,
+                    Scenes.Shade,
+                    flowOf(Scenes.Lockscreen),
+                    progress,
+                    false,
+                    flowOf(false)
+                )
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.AOD,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.FINISHED,
+                progress = 1f,
+            )
+        }
+
+    /**
+     * STL: Gone -> Ls, then interrupt in KTF LS -> AOD, then STL Ls -> Shade
+     *
+     * In this scenario it is important that the last STL transition Ls -> Shade triggers a cancel
+     * of the * -> AOD transition but then also properly starts a transition AOD (not LOCKSCREEN) ->
+     * UNDEFINED transition.
+     */
+    @Test
+    fun transition_to_ls_interrupted_by_ktf_transition_then_interrupted_by_from_ls_transition() =
+        testScope.runTest {
+            val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+            val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+            sceneTransitions.value = goneToLs
+            progress.value = 0.4f
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.RUNNING,
+                progress = 0.4f,
+            )
+
+            kosmos.realKeyguardTransitionRepository.startTransition(
+                TransitionInfo(
+                    ownerName = this.javaClass.simpleName,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    animator = null,
+                    modeOnCanceled = TransitionModeOnCanceled.RESET
+                )
+            )
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                state = TransitionState.STARTED,
+                progress = 0f,
+            )
+
+            progress.value = 0.7f
+            assertTransition(currentStep!!, progress = 0f)
+
+            sceneTransitions.value =
+                ObservableTransitionState.Transition(
+                    Scenes.Lockscreen,
+                    Scenes.Shade,
+                    flowOf(Scenes.Lockscreen),
+                    progress,
+                    false,
+                    flowOf(false)
+                )
+            allSteps[allSteps.size - 3]
+
+            assertTransition(
+                step = allSteps[allSteps.size - 3],
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                state = TransitionState.CANCELED,
+                progress = 0f,
+            )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 2],
+                from = KeyguardState.AOD,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.STARTED,
+                progress = 0f,
+            )
+
+            progress.value = 0.2f
+            assertTransition(
+                step = allSteps[allSteps.size - 1],
+                from = KeyguardState.AOD,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.RUNNING,
+                progress = 0.2f,
+            )
+        }
+
+    /**
+     * STL: Gone -> Ls, then interrupt in KTF LS -> AOD, then STL Shade -> Ls
+     *
+     * In this scenario it is important KTF is brought back into a FINISHED UNDEFINED state
+     * considering the state is already on AOD from where a new UNDEFINED -> LOCKSCREEN transition
+     * can be started.
+     */
+    @Test
+    fun transition_to_ls_interrupted_by_ktf_transition_then_interrupted_by_to_ls_transition() =
+        testScope.runTest {
+            val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+            val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+            sceneTransitions.value = goneToLs
+            progress.value = 0.4f
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.RUNNING,
+                progress = 0.4f,
+            )
+
+            kosmos.realKeyguardTransitionRepository.startTransition(
+                TransitionInfo(
+                    ownerName = this.javaClass.simpleName,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    animator = null,
+                    modeOnCanceled = TransitionModeOnCanceled.RESET
+                )
+            )
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                state = TransitionState.STARTED,
+                progress = 0f,
+            )
+
+            progress.value = 0.7f
+            assertTransition(currentStep!!, progress = 0f)
+
+            sceneTransitions.value =
+                ObservableTransitionState.Transition(
+                    Scenes.Shade,
+                    Scenes.Lockscreen,
+                    flowOf(Scenes.Lockscreen),
+                    progress,
+                    false,
+                    flowOf(false)
+                )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 5],
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                state = TransitionState.CANCELED,
+                progress = 0f,
+            )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 4],
+                from = KeyguardState.AOD,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.STARTED,
+                progress = 1f,
+            )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 3],
+                from = KeyguardState.AOD,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.FINISHED,
+                progress = 1f,
+            )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 2],
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.STARTED,
+                progress = 0f,
+            )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 1],
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.RUNNING,
+                progress = 0.7f,
+            )
+        }
+
+    /**
+     * STL: Gone -> Ls, then interrupt multiple canceled KTF transitions, then STL Ls -> Shade
+     *
+     * Similar to
+     * `transition_to_ls_scene_interrupted_by_ktf_transition_then_interrupted_by_from_ls_transition`
+     * but here KTF is canceled multiple times such that in the end OCCLUDED -> UNDEFINED is
+     * properly started. (not from AOD or LOCKSCREEN)
+     *
+     * Note: there is no test which tests multiple cancels from the STL side, this is because all
+     * STL transitions trigger a response from LockscreenSceneTransitionInteractor which forces KTF
+     * into a specific state, so testing each pair is enough. Meanwhile KTF can move around without
+     * any reaction from LockscreenSceneTransitionInteractor.
+     */
+    @Test
+    fun transition_to_ls_interrupted_by_ktf_cancel_sequence_interrupted_by_from_ls_transition() =
+        testScope.runTest {
+            val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+            val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+            sceneTransitions.value = lsToGone
+            progress.value = 0.4f
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.RUNNING,
+                progress = 0.4f,
+            )
+
+            kosmos.realKeyguardTransitionRepository.startTransition(
+                TransitionInfo(
+                    ownerName = this.javaClass.simpleName,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    animator = null,
+                    modeOnCanceled = TransitionModeOnCanceled.RESET
+                )
+            )
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                state = TransitionState.STARTED,
+                progress = 0f,
+            )
+
+            kosmos.realKeyguardTransitionRepository.startTransition(
+                TransitionInfo(
+                    ownerName = this.javaClass.simpleName,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.DOZING,
+                    animator = null,
+                    modeOnCanceled = TransitionModeOnCanceled.RESET
+                )
+            )
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.AOD,
+                to = KeyguardState.DOZING,
+                state = TransitionState.STARTED,
+                progress = 0f,
+            )
+
+            kosmos.realKeyguardTransitionRepository.startTransition(
+                TransitionInfo(
+                    ownerName = this.javaClass.simpleName,
+                    from = KeyguardState.DOZING,
+                    to = KeyguardState.OCCLUDED,
+                    animator = null,
+                    modeOnCanceled = TransitionModeOnCanceled.RESET
+                )
+            )
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.DOZING,
+                to = KeyguardState.OCCLUDED,
+                state = TransitionState.STARTED,
+                progress = 0f,
+            )
+
+            progress.value = 0.7f
+            assertTransition(currentStep!!, progress = 0f)
+
+            sceneTransitions.value =
+                ObservableTransitionState.Transition(
+                    Scenes.Lockscreen,
+                    Scenes.Shade,
+                    flowOf(Scenes.Lockscreen),
+                    progress,
+                    false,
+                    flowOf(false)
+                )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 3],
+                from = KeyguardState.DOZING,
+                to = KeyguardState.OCCLUDED,
+                state = TransitionState.CANCELED,
+                progress = 0f,
+            )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 2],
+                from = KeyguardState.OCCLUDED,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.STARTED,
+                progress = 0f,
+            )
+
+            progress.value = 0.2f
+            assertTransition(
+                step = allSteps[allSteps.size - 1],
+                from = KeyguardState.OCCLUDED,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.RUNNING,
+                progress = 0.2f,
+            )
+        }
+
+    /**
+     * STL: Gone -> Ls, then interrupted by KTF LS -> AOD which is FINISHED before STL Ls -> Shade
+     *
+     * Similar to
+     * `transition_to_ls_scene_interrupted_by_ktf_transition_then_interrupted_by_from_ls_transition`
+     * but here KTF is finishing the transition and only then gets interrupted. Should correctly
+     * start AOD -> UNDEFINED.
+     */
+    @Test
+    fun transition_to_ls_scene_interrupted_and_finished_by_ktf_interrupted_by_from_ls_transition() =
+        testScope.runTest {
+            val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+            val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+            sceneTransitions.value = lsToGone
+            progress.value = 0.4f
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.RUNNING,
+                progress = 0.4f,
+            )
+
+            val ktfUuid =
+                kosmos.realKeyguardTransitionRepository.startTransition(
+                    TransitionInfo(
+                        ownerName = this.javaClass.simpleName,
+                        from = KeyguardState.LOCKSCREEN,
+                        to = KeyguardState.AOD,
+                        animator = null,
+                        modeOnCanceled = TransitionModeOnCanceled.RESET
+                    )
+                )
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                state = TransitionState.STARTED,
+                progress = 0f,
+            )
+
+            kosmos.realKeyguardTransitionRepository.updateTransition(
+                ktfUuid!!,
+                1f,
+                TransitionState.FINISHED
+            )
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                state = TransitionState.FINISHED,
+                progress = 1f,
+            )
+
+            sceneTransitions.value =
+                ObservableTransitionState.Transition(
+                    Scenes.Lockscreen,
+                    Scenes.Shade,
+                    flowOf(Scenes.Lockscreen),
+                    progress,
+                    false,
+                    flowOf(false)
+                )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 3],
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                state = TransitionState.FINISHED,
+                progress = 1f,
+            )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 2],
+                from = KeyguardState.AOD,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.STARTED,
+                progress = 0f,
+            )
+
+            progress.value = 0.2f
+            assertTransition(
+                step = allSteps[allSteps.size - 1],
+                from = KeyguardState.AOD,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.RUNNING,
+                progress = 0.2f,
+            )
+        }
+
+    /**
+     * STL: Ls -> Gone, then interrupted by Ls -> Bouncer. This happens when the next transition is
+     * immediately started from Gone without settling in Idle. This specifically happens when
+     * dragging down on Ls and then changing direction. The transition will switch from -> Shade to
+     * -> Bouncer without settling or signaling any cancellation as STL considers this to be the
+     * same gesture.
+     *
+     * In STL there is no guarantee that transitions settle in Idle before continuing.
+     */
+    @Test
+    fun transition_from_ls_scene_interrupted_by_other_from_ls_transition() =
+        testScope.runTest {
+            val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+            val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+            sceneTransitions.value = lsToGone
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.RUNNING,
+                progress = 0f,
+            )
+
+            progress.value = 0.4f
+            sceneTransitions.value =
+                ObservableTransitionState.Transition(
+                    Scenes.Lockscreen,
+                    Scenes.Bouncer,
+                    flowOf(Scenes.Lockscreen),
+                    progress,
+                    false,
+                    flowOf(false)
+                )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 5],
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.CANCELED,
+                progress = 0.4f,
+            )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 4],
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.STARTED,
+                progress = 0.6f,
+            )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 3],
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.FINISHED,
+                progress = 1f,
+            )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 2],
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.STARTED,
+                progress = 0f,
+            )
+        }
+
+    /**
+     * STL: Ls -> Gone, then interrupted by Gone -> Ls. This happens when the next transition is
+     * immediately started from Gone without settling in Idle. In STL there is no guarantee that
+     * transitions settle in Idle before continuing.
+     */
+    @Test
+    fun transition_from_ls_scene_interrupted_by_to_ls_transition() =
+        testScope.runTest {
+            val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+            val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+            sceneTransitions.value = lsToGone
+            progress.value = 0.4f
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.RUNNING,
+                progress = 0.4f,
+            )
+
+            sceneTransitions.value =
+                ObservableTransitionState.Transition(
+                    Scenes.Gone,
+                    Scenes.Lockscreen,
+                    flowOf(Scenes.Lockscreen),
+                    progress,
+                    false,
+                    flowOf(false)
+                )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 3],
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.FINISHED,
+                progress = 1f,
+            )
+
+            assertTransition(
+                step = allSteps[allSteps.size - 2],
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.STARTED,
+                progress = 0f,
+            )
+
+            progress.value = 0.2f
+            assertTransition(
+                step = allSteps[allSteps.size - 1],
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.RUNNING,
+                progress = 0.2f,
+            )
+        }
+
+    /**
+     * STL: Ls -> Gone, then interrupted by Gone -> Bouncer. This happens when the next transition
+     * is immediately started from Gone without settling in Idle. In STL there is no guarantee that
+     * transitions settle in Idle before continuing.
+     */
+    @Test
+    fun transition_from_ls_scene_interrupted_by_other_stl_transition() =
+        testScope.runTest {
+            val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+            sceneTransitions.value = lsToGone
+            progress.value = 0.4f
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.RUNNING,
+                progress = 0.4f,
+            )
+
+            sceneTransitions.value =
+                ObservableTransitionState.Transition(
+                    Scenes.Gone,
+                    Scenes.Bouncer,
+                    flowOf(Scenes.Lockscreen),
+                    progress,
+                    false,
+                    flowOf(false)
+                )
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.FINISHED,
+                progress = 1f,
+            )
+        }
+
+    private fun assertTransition(
+        step: TransitionStep,
+        from: KeyguardState? = null,
+        to: KeyguardState? = null,
+        state: TransitionState? = null,
+        progress: Float? = null
+    ) {
+        if (from != null) assertThat(step.from).isEqualTo(from)
+        if (to != null) assertThat(step.to).isEqualTo(to)
+        if (state != null) assertThat(step.transitionState).isEqualTo(state)
+        if (progress != null) assertThat(step.value).isEqualTo(progress)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
index 2f650c4..040d3b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
@@ -85,7 +85,6 @@
         setupWeatherClock()
         KeyguardClockViewBinder.updateBurnInLayer(rootView, clockViewModel, ClockSize.LARGE)
         verify(burnInLayer).removeView(smallClockView)
-        verify(burnInLayer).addView(largeClockView)
     }
 
     @Test
@@ -101,7 +100,6 @@
         setupNonWeatherClock()
         KeyguardClockViewBinder.updateBurnInLayer(rootView, clockViewModel, ClockSize.SMALL)
         verify(burnInLayer).addView(smallClockView)
-        verify(burnInLayer).removeView(largeClockView)
     }
 
     private fun setupWeatherClock() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
index 9b2db3e..1f13298 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
@@ -41,10 +42,9 @@
     private lateinit var executor: FakeExecutor
 
     @Mock private lateinit var activityTaskManagerService: IActivityTaskManager
-
     @Mock private lateinit var keyguardStateController: KeyguardStateController
-
     @Mock private lateinit var keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier
+    @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
 
     @Before
     fun setUp() {
@@ -57,6 +57,7 @@
                 activityTaskManagerService = activityTaskManagerService,
                 keyguardStateController = keyguardStateController,
                 keyguardSurfaceBehindAnimator = keyguardSurfaceBehindAnimator,
+                keyguardTransitionInteractor = keyguardTransitionInteractor,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index ba2efe6..b3cc5c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardSmartspaceInteractor
 import com.android.systemui.keyguard.shared.model.ClockSize
 import com.android.systemui.keyguard.ui.viewmodel.keyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
 import com.android.systemui.keyguard.ui.viewmodel.keyguardSmartspaceViewModel
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
@@ -117,6 +118,7 @@
                     context,
                     keyguardSmartspaceViewModel,
                     { keyguardBlueprintInteractor },
+                    keyguardRootViewModel,
                 )
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt
index 1396b20..391831a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt
@@ -18,15 +18,19 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import androidx.test.filters.SmallTest
+import com.android.keyguard.keyguardUpdateMonitor
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.policy.keyguardStateController
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -34,6 +38,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import org.mockito.kotlin.whenever
 
 @ExperimentalCoroutinesApi
 @RunWith(JUnit4::class)
@@ -49,13 +54,35 @@
     fun alternateBouncerTransition_alternateBouncerWindowRequiredTrue() =
         testScope.runTest {
             mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+            val canShowAlternateBouncer by collectLastValue(underTest.canShowAlternateBouncer)
             val alternateBouncerWindowRequired by
                 collectLastValue(underTest.alternateBouncerWindowRequired)
+            givenCanShowAlternateBouncer()
             fingerprintPropertyRepository.supportsUdfps()
             transitionRepository.sendTransitionSteps(
                 listOf(
+                    stepToLockscreen(0f, TransitionState.STARTED),
+                    stepToLockscreen(.4f),
+                    stepToLockscreen(1f, TransitionState.FINISHED),
+                ),
+                testScope,
+            )
+            assertThat(canShowAlternateBouncer).isTrue()
+            transitionRepository.sendTransitionSteps(
+                listOf(
+                    stepFromLockscreenToAlternateBouncer(0f, TransitionState.STARTED),
+                    stepFromLockscreenToAlternateBouncer(.4f),
+                    stepFromLockscreenToAlternateBouncer(.6f),
+                ),
+                testScope,
+            )
+            assertThat(canShowAlternateBouncer).isTrue()
+            assertThat(alternateBouncerWindowRequired).isTrue()
+
+            transitionRepository.sendTransitionSteps(
+                listOf(
                     stepFromAlternateBouncer(0f, TransitionState.STARTED),
-                    stepFromAlternateBouncer(.4f),
+                    stepFromAlternateBouncer(.2f),
                     stepFromAlternateBouncer(.6f),
                 ),
                 testScope,
@@ -77,13 +104,21 @@
             mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
             val alternateBouncerWindowRequired by
                 collectLastValue(underTest.alternateBouncerWindowRequired)
+            givenCanShowAlternateBouncer()
             fingerprintPropertyRepository.supportsUdfps()
             transitionRepository.sendTransitionSteps(
                 listOf(
-                    stepFromAlternateBouncer(0f, TransitionState.STARTED),
-                    stepFromAlternateBouncer(.4f),
-                    stepFromAlternateBouncer(.6f),
-                    stepFromAlternateBouncer(1f),
+                    stepToLockscreen(0f, TransitionState.STARTED),
+                    stepToLockscreen(.4f),
+                    stepToLockscreen(1f, TransitionState.FINISHED),
+                ),
+                testScope,
+            )
+            transitionRepository.sendTransitionSteps(
+                listOf(
+                    stepFromLockscreenToAlternateBouncer(0f, TransitionState.STARTED),
+                    stepFromLockscreenToAlternateBouncer(.4f),
+                    stepFromLockscreenToAlternateBouncer(.6f),
                 ),
                 testScope,
             )
@@ -96,13 +131,23 @@
             mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
             val alternateBouncerWindowRequired by
                 collectLastValue(underTest.alternateBouncerWindowRequired)
+            givenCanShowAlternateBouncer()
             fingerprintPropertyRepository.supportsUdfps()
             transitionRepository.sendTransitionSteps(
                 listOf(
+                    stepFromLockscreenToDozing(0f, TransitionState.STARTED),
+                    stepFromLockscreenToDozing(.4f),
+                    stepFromLockscreenToDozing(.6f),
+                    stepFromLockscreenToDozing(1f, TransitionState.FINISHED),
+                ),
+                testScope,
+            )
+            assertThat(alternateBouncerWindowRequired).isFalse()
+            transitionRepository.sendTransitionSteps(
+                listOf(
                     stepFromDozingToLockscreen(0f, TransitionState.STARTED),
                     stepFromDozingToLockscreen(.4f),
                     stepFromDozingToLockscreen(.6f),
-                    stepFromDozingToLockscreen(1f),
                 ),
                 testScope,
             )
@@ -115,19 +160,39 @@
             mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
             val alternateBouncerWindowRequired by
                 collectLastValue(underTest.alternateBouncerWindowRequired)
+            givenCanShowAlternateBouncer()
             fingerprintPropertyRepository.supportsRearFps()
             transitionRepository.sendTransitionSteps(
                 listOf(
-                    stepFromAlternateBouncer(0f, TransitionState.STARTED),
-                    stepFromAlternateBouncer(.4f),
-                    stepFromAlternateBouncer(.6f),
-                    stepFromAlternateBouncer(1f),
+                    stepToLockscreen(0f, TransitionState.STARTED),
+                    stepToLockscreen(.4f),
+                    stepToLockscreen(1f, TransitionState.FINISHED),
+                ),
+                testScope,
+            )
+            transitionRepository.sendTransitionSteps(
+                listOf(
+                    stepFromLockscreenToAlternateBouncer(0f, TransitionState.STARTED),
+                    stepFromLockscreenToAlternateBouncer(.4f),
+                    stepFromLockscreenToAlternateBouncer(.6f),
                 ),
                 testScope,
             )
             assertThat(alternateBouncerWindowRequired).isFalse()
         }
 
+    private fun stepToLockscreen(
+        value: Float,
+        state: TransitionState = TransitionState.RUNNING
+    ): TransitionStep {
+        return step(
+            from = KeyguardState.GONE,
+            to = KeyguardState.LOCKSCREEN,
+            value = value,
+            transitionState = state,
+        )
+    }
+
     private fun stepFromAlternateBouncer(
         value: Float,
         state: TransitionState = TransitionState.RUNNING
@@ -140,6 +205,18 @@
         )
     }
 
+    private fun stepFromLockscreenToAlternateBouncer(
+        value: Float,
+        state: TransitionState = TransitionState.RUNNING
+    ): TransitionStep {
+        return step(
+            from = KeyguardState.LOCKSCREEN,
+            to = KeyguardState.ALTERNATE_BOUNCER,
+            value = value,
+            transitionState = state,
+        )
+    }
+
     private fun stepFromDozingToLockscreen(
         value: Float,
         state: TransitionState = TransitionState.RUNNING
@@ -152,6 +229,18 @@
         )
     }
 
+    private fun stepFromLockscreenToDozing(
+        value: Float,
+        state: TransitionState = TransitionState.RUNNING
+    ): TransitionStep {
+        return step(
+            from = KeyguardState.LOCKSCREEN,
+            to = KeyguardState.DOZING,
+            value = value,
+            transitionState = state,
+        )
+    }
+
     private fun step(
         from: KeyguardState,
         to: KeyguardState,
@@ -166,4 +255,16 @@
             ownerName = "AlternateBouncerViewModelTest"
         )
     }
+
+    /**
+     * Given the alternate bouncer parameters are set so that the alternate bouncer can show, aside
+     * from the fingerprint modality.
+     */
+    private fun givenCanShowAlternateBouncer() {
+        kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(false)
+        kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+        kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+        whenever(kosmos.keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
+        whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
index 0bca367..f61ddeb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
@@ -19,6 +19,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.fakeAccessibilityRepository
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
 import com.android.systemui.coroutines.collectLastValue
@@ -150,6 +151,51 @@
             assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.NONE)
         }
 
+    fun accessibilityDelegateHint_accessibilityNotEnabled() =
+        testScope.runTest {
+            val accessibilityDelegateHint by collectLastValue(underTest.accessibilityDelegateHint)
+            kosmos.fakeAccessibilityRepository.isEnabled.value = false
+            assertThat(accessibilityDelegateHint)
+                .isEqualTo(DeviceEntryIconView.AccessibilityHintType.NONE)
+        }
+
+    @Test
+    fun accessibilityDelegateHint_accessibilityEnabled_locked() =
+        testScope.runTest {
+            val accessibilityDelegateHint by collectLastValue(underTest.accessibilityDelegateHint)
+            kosmos.fakeAccessibilityRepository.isEnabled.value = true
+
+            // interactive lock icon
+            keyguardRepository.setKeyguardDismissible(false)
+            fingerprintPropertyRepository.supportsUdfps()
+
+            assertThat(accessibilityDelegateHint)
+                .isEqualTo(DeviceEntryIconView.AccessibilityHintType.AUTHENTICATE)
+
+            // non-interactive lock icon
+            keyguardRepository.setKeyguardDismissible(false)
+            fingerprintPropertyRepository.supportsRearFps()
+
+            assertThat(accessibilityDelegateHint)
+                .isEqualTo(DeviceEntryIconView.AccessibilityHintType.NONE)
+        }
+
+    @Test
+    fun accessibilityDelegateHint_accessibilityEnabled_unlocked() =
+        testScope.runTest {
+            val accessibilityDelegateHint by collectLastValue(underTest.accessibilityDelegateHint)
+            kosmos.fakeAccessibilityRepository.isEnabled.value = true
+
+            // interactive unlock icon
+            keyguardRepository.setKeyguardDismissible(true)
+            fingerprintPropertyRepository.supportsUdfps()
+            advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
+            runCurrent()
+
+            assertThat(accessibilityDelegateHint)
+                .isEqualTo(DeviceEntryIconView.AccessibilityHintType.ENTER)
+        }
+
     private fun deviceEntryIconTransitionAlpha(alpha: Float) {
         deviceEntryIconTransition.setDeviceEntryParentViewAlpha(alpha)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 1881a9e..16421a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -88,7 +88,7 @@
 
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
 
     @Mock private lateinit var expandable: Expandable
     @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
@@ -115,7 +115,7 @@
     private val kosmos = testKosmos()
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 0c98cff..40663ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.BrokenWithSceneContainer
 import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
 import com.android.systemui.keyguard.data.repository.keyguardClockRepository
@@ -53,10 +54,10 @@
 
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class KeyguardClockViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     val kosmos = testKosmos()
     val testScope = kosmos.testScope
-    val underTest = kosmos.keyguardClockViewModel
+    val underTest by lazy { kosmos.keyguardClockViewModel }
     val res = context.resources
 
     @Mock lateinit var clockController: ClockController
@@ -67,7 +68,7 @@
     var faceConfig = ClockFaceConfig()
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
@@ -96,6 +97,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun currentClockLayout_splitShadeOn_clockNotCentered_largeClock_splitShadeLargeClock() =
         testScope.runTest {
             val currentClockLayout by collectLastValue(underTest.currentClockLayout)
@@ -110,6 +112,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun currentClockLayout_splitShadeOn_clockNotCentered_smallClock_splitShadeSmallClock() =
         testScope.runTest {
             val currentClockLayout by collectLastValue(underTest.currentClockLayout)
@@ -124,6 +127,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun currentClockLayout_singleShade_smallClock_smallClock() =
         testScope.runTest {
             val currentClockLayout by collectLastValue(underTest.currentClockLayout)
@@ -193,6 +197,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun testClockSize_dynamicClockSize() =
         testScope.runTest {
             with(kosmos) {
@@ -216,6 +221,7 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(339465026)
     fun isLargeClockVisible_whenSmallClockSize_isFalse() =
         testScope.runTest {
             val value by collectLastValue(underTest.isLargeClockVisible)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt
index e56a253..5986f4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt
@@ -33,9 +33,6 @@
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.Executor
@@ -50,6 +47,9 @@
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
 
 private const val KEY = "TEST_KEY"
 private const val KEY_ALT = "TEST_KEY_2"
@@ -172,20 +172,20 @@
     fun testOnRemovedForCurrent_callsListener() {
         // GIVEN a media was removed for main user
         mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
-        mediaDataFilter.onMediaDataRemoved(KEY)
+        mediaDataFilter.onMediaDataRemoved(KEY, false)
 
         // THEN we should tell the listener
-        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
     }
 
     @Test
     fun testOnRemovedForGuest_doesNotCallListener() {
         // GIVEN a media was removed for guest user
         mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
-        mediaDataFilter.onMediaDataRemoved(KEY)
+        mediaDataFilter.onMediaDataRemoved(KEY, false)
 
         // THEN we should NOT tell the listener
-        verify(listener, never()).onMediaDataRemoved(eq(KEY))
+        verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
     }
 
     @Test
@@ -197,7 +197,7 @@
         setUser(USER_GUEST)
 
         // THEN we should remove the main user's media
-        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
     }
 
     @Test
@@ -230,7 +230,7 @@
         setPrivateProfileUnavailable()
 
         // THEN we should add the private profile media
-        verify(listener).onMediaDataRemoved(eq(KEY_ALT))
+        verify(listener).onMediaDataRemoved(eq(KEY_ALT), eq(false))
     }
 
     @Test
@@ -360,7 +360,7 @@
     @Test
     fun testOnNotificationRemoved_doesntHaveMedia() {
         mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
-        mediaDataFilter.onMediaDataRemoved(KEY)
+        mediaDataFilter.onMediaDataRemoved(KEY, false)
         assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isFalse()
         assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
index 5a2d22d..3372f06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
@@ -66,9 +66,6 @@
 import com.android.systemui.statusbar.SbnBuilder
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
@@ -90,6 +87,9 @@
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoSession
 import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
+import org.mockito.kotlin.capture
+import org.mockito.kotlin.eq
 import org.mockito.quality.Strictness
 
 private const val KEY = "KEY"
@@ -346,7 +346,7 @@
         // THEN it is removed and listeners are informed
         foregroundExecutor.advanceClockToLast()
         foregroundExecutor.runAllReady()
-        verify(listener).onMediaDataRemoved(PACKAGE_NAME)
+        verify(listener).onMediaDataRemoved(PACKAGE_NAME, false)
     }
 
     @Test
@@ -532,7 +532,7 @@
         addNotificationAndLoad()
         val data = mediaDataCaptor.value
         mediaDataManager.onNotificationRemoved(KEY)
-        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
         verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
     }
 
@@ -777,7 +777,7 @@
                 eq(false)
             )
         assertThat(mediaDataCaptor.value.resumption).isTrue()
-        verify(listener, never()).onMediaDataRemoved(eq(KEY))
+        verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false))
         // WHEN the second is removed
         mediaDataManager.onNotificationRemoved(KEY_2)
         // THEN the data is for resumption and the second key is removed
@@ -791,7 +791,7 @@
                 eq(false)
             )
         assertThat(mediaDataCaptor.value.resumption).isTrue()
-        verify(listener).onMediaDataRemoved(eq(KEY_2))
+        verify(listener).onMediaDataRemoved(eq(KEY_2), eq(false))
     }
 
     @Test
@@ -816,7 +816,7 @@
         mediaDataManager.onNotificationRemoved(KEY)
 
         // THEN the media data is removed
-        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
     }
 
     @Test
@@ -866,7 +866,7 @@
         mediaDataManager.onNotificationRemoved(KEY)
 
         // THEN the media data is removed
-        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
     }
 
     @Test
@@ -905,7 +905,7 @@
         assertThat(mediaDataCaptor.value.isPlaying).isFalse()
 
         // And the oldest resume control was removed
-        verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"))
+        verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"), eq(false))
     }
 
     fun testOnNotificationRemoved_lockDownMode() {
@@ -915,7 +915,7 @@
         val data = mediaDataCaptor.value
         mediaDataManager.onNotificationRemoved(KEY)
 
-        verify(listener, never()).onMediaDataRemoved(eq(KEY))
+        verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
         verify(logger, never())
             .logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
         verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
@@ -1148,7 +1148,7 @@
         mediaDataManager.setMediaResumptionEnabled(false)
 
         // THEN the resume controls are dismissed
-        verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME))
+        verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME), eq(false))
         verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
     }
 
@@ -1156,19 +1156,19 @@
     fun testDismissMedia_listenerCalled() {
         addNotificationAndLoad()
         val data = mediaDataCaptor.value
-        val removed = mediaDataManager.dismissMediaData(KEY, 0L)
+        val removed = mediaDataManager.dismissMediaData(KEY, 0L, true)
         assertThat(removed).isTrue()
 
         foregroundExecutor.advanceClockToLast()
         foregroundExecutor.runAllReady()
 
-        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(listener).onMediaDataRemoved(eq(KEY), eq(true))
         verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
     }
 
     @Test
     fun testDismissMedia_keyDoesNotExist_returnsFalse() {
-        val removed = mediaDataManager.dismissMediaData(KEY, 0L)
+        val removed = mediaDataManager.dismissMediaData(KEY, 0L, true)
         assertThat(removed).isFalse()
     }
 
@@ -2077,7 +2077,7 @@
         sessionCallbackCaptor.value.invoke(KEY)
 
         // It remains as a regular player
-        verify(listener, never()).onMediaDataRemoved(eq(KEY))
+        verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
         verify(listener, never())
             .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
     }
@@ -2093,7 +2093,7 @@
         mediaDataManager.onNotificationRemoved(KEY)
 
         // It is fully removed
-        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
         verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
         verify(listener, never())
             .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2146,7 +2146,7 @@
         mediaDataManager.onNotificationRemoved(KEY)
 
         // It remains as a regular player
-        verify(listener, never()).onMediaDataRemoved(eq(KEY))
+        verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
         verify(listener, never())
             .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
     }
@@ -2199,7 +2199,7 @@
         sessionCallbackCaptor.value.invoke(KEY)
 
         // It is fully removed
-        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
         verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
         verify(listener, never())
             .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2253,7 +2253,7 @@
         sessionCallbackCaptor.value.invoke(KEY)
 
         // It is fully removed.
-        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
         verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
         verify(listener, never())
             .onMediaDataLoaded(
@@ -2279,7 +2279,7 @@
         sessionCallbackCaptor.value.invoke(KEY)
 
         // It is fully removed
-        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
         verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
         verify(listener, never())
             .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2329,7 +2329,7 @@
         mediaDataManager.onNotificationRemoved(KEY)
 
         // We still make sure to remove it
-        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
index bb5b572..dd05a0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
@@ -202,24 +202,24 @@
     @Test
     public void mediaDataRemoved() {
         // WHEN media data is removed without first receiving device or data
-        mManager.onMediaDataRemoved(KEY);
+        mManager.onMediaDataRemoved(KEY, false);
         // THEN a removed event isn't emitted
-        verify(mListener, never()).onMediaDataRemoved(eq(KEY));
+        verify(mListener, never()).onMediaDataRemoved(eq(KEY), anyBoolean());
     }
 
     @Test
     public void mediaDataRemovedAfterMediaEvent() {
         mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */,
                 0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
-        mManager.onMediaDataRemoved(KEY);
-        verify(mListener).onMediaDataRemoved(eq(KEY));
+        mManager.onMediaDataRemoved(KEY, false);
+        verify(mListener).onMediaDataRemoved(eq(KEY), eq(false));
     }
 
     @Test
     public void mediaDataRemovedAfterDeviceEvent() {
         mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
-        mManager.onMediaDataRemoved(KEY);
-        verify(mListener).onMediaDataRemoved(eq(KEY));
+        mManager.onMediaDataRemoved(KEY, false);
+        verify(mListener).onMediaDataRemoved(eq(KEY), eq(false));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
index 857af66..caaa42f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
@@ -40,9 +40,6 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.Executor
@@ -60,6 +57,9 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
 
 private const val KEY = "TEST_KEY"
 private const val KEY_ALT = "TEST_KEY_2"
@@ -168,7 +168,7 @@
     @Test
     fun onDataLoadedForCurrentUser_updatesLoadedStates() =
         testScope.runTest {
-            val sortedMedia by collectLastValue(repository.sortedMedia)
+            val currentMedia by collectLastValue(repository.currentMedia)
             val mediaCommonModel =
                 MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(dataMain.instanceId))
 
@@ -176,13 +176,13 @@
 
             verify(listener)
                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true), eq(0), eq(false))
-            assertThat(sortedMedia?.values).containsExactly(mediaCommonModel)
+            assertThat(currentMedia).containsExactly(mediaCommonModel)
         }
 
     @Test
     fun onDataLoadedForGuest_doesNotUpdateLoadedStates() =
         testScope.runTest {
-            val sortedMedia by collectLastValue(repository.sortedMedia)
+            val currentMedia by collectLastValue(repository.currentMedia)
             val mediaCommonModel =
                 MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(dataMain.instanceId))
 
@@ -190,64 +190,63 @@
 
             verify(listener, never())
                 .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
-            assertThat(sortedMedia?.values).doesNotContain(mediaCommonModel)
+            assertThat(currentMedia).doesNotContain(mediaCommonModel)
         }
 
     @Test
     fun onRemovedForCurrent_updatesLoadedStates() =
         testScope.runTest {
-            val sortedMedia by collectLastValue(repository.sortedMedia)
+            val currentMedia by collectLastValue(repository.currentMedia)
             val mediaCommonModel =
                 MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(dataMain.instanceId))
 
             // GIVEN a media was removed for main user
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
 
-            assertThat(sortedMedia?.values).containsExactly(mediaCommonModel)
+            assertThat(currentMedia).containsExactly(mediaCommonModel)
 
-            mediaDataFilter.onMediaDataRemoved(KEY)
+            mediaDataFilter.onMediaDataRemoved(KEY, false)
 
-            verify(listener).onMediaDataRemoved(eq(KEY))
-            assertThat(sortedMedia?.values).doesNotContain(mediaCommonModel)
+            verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
+            assertThat(currentMedia).doesNotContain(mediaCommonModel)
         }
 
     @Test
     fun onRemovedForGuest_doesNotUpdateLoadedStates() =
         testScope.runTest {
-            val sortedMedia by collectLastValue(repository.sortedMedia)
+            val currentMedia by collectLastValue(repository.currentMedia)
 
             // GIVEN a media was removed for guest user
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
-            mediaDataFilter.onMediaDataRemoved(KEY)
+            mediaDataFilter.onMediaDataRemoved(KEY, false)
 
-            verify(listener, never()).onMediaDataRemoved(eq(KEY))
-            assertThat(sortedMedia).isEmpty()
+            verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false))
+            assertThat(currentMedia).isEmpty()
         }
 
     @Test
     fun onUserSwitched_removesOldUserControls() =
         testScope.runTest {
-            val sortedMedia by collectLastValue(repository.sortedMedia)
+            val currentMedia by collectLastValue(repository.currentMedia)
             val mediaLoaded = MediaDataLoadingModel.Loaded(dataMain.instanceId)
 
             // GIVEN that we have a media loaded for main user
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
 
-            assertThat(sortedMedia?.values)
-                .containsExactly(MediaCommonModel.MediaControl(mediaLoaded))
+            assertThat(currentMedia).containsExactly(MediaCommonModel.MediaControl(mediaLoaded))
 
             // and we switch to guest user
             setUser(USER_GUEST)
 
             // THEN we should remove the main user's media
-            verify(listener).onMediaDataRemoved(eq(KEY))
-            assertThat(sortedMedia).isEmpty()
+            verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
+            assertThat(currentMedia).isEmpty()
         }
 
     @Test
     fun onUserSwitched_addsNewUserControls() =
         testScope.runTest {
-            val sortedMedia by collectLastValue(repository.sortedMedia)
+            val currentMedia by collectLastValue(repository.currentMedia)
             val guestLoadedStatesModel = MediaDataLoadingModel.Loaded(dataGuest.instanceId)
             val mainLoadedStatesModel = MediaDataLoadingModel.Loaded(dataMain.instanceId)
 
@@ -272,16 +271,16 @@
                     anyInt(),
                     anyBoolean()
                 )
-            assertThat(sortedMedia?.values)
+            assertThat(currentMedia)
                 .containsExactly(MediaCommonModel.MediaControl(guestLoadedStatesModel))
-            assertThat(sortedMedia?.values)
+            assertThat(currentMedia)
                 .doesNotContain(MediaCommonModel.MediaControl(mainLoadedStatesModel))
         }
 
     @Test
     fun onProfileChanged_profileUnavailable_updateStates() =
         testScope.runTest {
-            val sortedMedia by collectLastValue(repository.sortedMedia)
+            val currentMedia by collectLastValue(repository.currentMedia)
 
             // GIVEN that we had some media for both profiles
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
@@ -292,8 +291,8 @@
 
             val mediaLoadedStatesModel = MediaDataLoadingModel.Loaded(dataMain.instanceId)
             // THEN we should remove the private profile media
-            verify(listener).onMediaDataRemoved(eq(KEY_ALT))
-            assertThat(sortedMedia?.values)
+            verify(listener).onMediaDataRemoved(eq(KEY_ALT), eq(false))
+            assertThat(currentMedia)
                 .containsExactly(MediaCommonModel.MediaControl(mediaLoadedStatesModel))
         }
 
@@ -503,7 +502,7 @@
             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
 
             mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
-            mediaDataFilter.onMediaDataRemoved(KEY)
+            mediaDataFilter.onMediaDataRemoved(KEY, false)
             assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData))
                 .isFalse()
             assertThat(hasAnyMedia(selectedUserEntries)).isFalse()
@@ -523,13 +522,13 @@
             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
             val reactivatedKey by collectLastValue(repository.reactivatedId)
-            val sortedMedia by collectLastValue(repository.sortedMedia)
+            val currentMedia by collectLastValue(repository.currentMedia)
             val recommendationsLoadingModel =
                 SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY, isPrioritized = true)
 
             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
-            assertThat(sortedMedia?.values)
+            assertThat(currentMedia)
                 .containsExactly(MediaCommonModel.MediaRecommendations(recommendationsLoadingModel))
             assertThat(
                     hasActiveMediaOrRecommendation(
@@ -552,13 +551,13 @@
             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
             val reactivatedKey by collectLastValue(repository.reactivatedId)
-            val sortedMedia by collectLastValue(repository.sortedMedia)
+            val currentMedia by collectLastValue(repository.currentMedia)
 
             whenever(smartspaceData.isActive).thenReturn(false)
 
             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
-            assertThat(sortedMedia).isEmpty()
+            assertThat(currentMedia).isEmpty()
             assertThat(
                     hasActiveMediaOrRecommendation(
                         selectedUserEntries,
@@ -581,7 +580,7 @@
             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
             val reactivatedKey by collectLastValue(repository.reactivatedId)
-            val sortedMedia by collectLastValue(repository.sortedMedia)
+            val currentMedia by collectLastValue(repository.currentMedia)
             val recsCommonModel =
                 MediaCommonModel.MediaRecommendations(
                     SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY, isPrioritized = true)
@@ -596,7 +595,7 @@
             clock.advanceTime(MediaDataFilterImpl.SMARTSPACE_MAX_AGE + 100)
             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
-            assertThat(sortedMedia?.values).containsExactly(recsCommonModel, controlCommonModel)
+            assertThat(currentMedia).containsExactly(recsCommonModel, controlCommonModel)
             assertThat(
                     hasActiveMediaOrRecommendation(
                         selectedUserEntries,
@@ -618,7 +617,7 @@
             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
             val reactivatedKey by collectLastValue(repository.reactivatedId)
-            val sortedMedia by collectLastValue(repository.sortedMedia)
+            val currentMedia by collectLastValue(repository.currentMedia)
             whenever(smartspaceData.isActive).thenReturn(false)
 
             val dataOld = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
@@ -626,7 +625,7 @@
             clock.advanceTime(MediaDataFilterImpl.SMARTSPACE_MAX_AGE + 100)
             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
-            assertThat(sortedMedia?.values)
+            assertThat(currentMedia)
                 .doesNotContain(
                     MediaCommonModel.MediaRecommendations(
                         SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
@@ -652,7 +651,7 @@
             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
             val reactivatedKey by collectLastValue(repository.reactivatedId)
-            val sortedMedia by collectLastValue(repository.sortedMedia)
+            val currentMedia by collectLastValue(repository.currentMedia)
 
             whenever(smartspaceData.isActive).thenReturn(false)
 
@@ -665,7 +664,7 @@
                 )
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
 
-            assertThat(sortedMedia?.values).containsExactly(controlCommonModel)
+            assertThat(currentMedia).containsExactly(controlCommonModel)
             verify(listener)
                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
 
@@ -673,7 +672,7 @@
             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
             // THEN we should treat the media as not active instead
-            assertThat(sortedMedia?.values).containsExactly(controlCommonModel)
+            assertThat(currentMedia).containsExactly(controlCommonModel)
             assertThat(
                     hasActiveMediaOrRecommendation(
                         selectedUserEntries,
@@ -696,7 +695,7 @@
             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
             val reactivatedKey by collectLastValue(repository.reactivatedId)
-            val sortedMedia by collectLastValue(repository.sortedMedia)
+            val currentMedia by collectLastValue(repository.currentMedia)
             whenever(smartspaceData.isValid()).thenReturn(false)
 
             // WHEN we have media that was recently played, but not currently active
@@ -707,7 +706,7 @@
                     true
                 )
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
-            assertThat(sortedMedia?.values).containsExactly(controlCommonModel)
+            assertThat(currentMedia).containsExactly(controlCommonModel)
             verify(listener)
                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
 
@@ -717,7 +716,7 @@
 
             // THEN we should treat the media as active instead
             val dataCurrentAndActive = dataCurrent.copy(active = true)
-            assertThat(sortedMedia?.values).containsExactly(controlCommonModel)
+            assertThat(currentMedia).containsExactly(controlCommonModel)
             assertThat(
                     hasActiveMediaOrRecommendation(
                         selectedUserEntries,
@@ -747,7 +746,7 @@
             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
             val reactivatedKey by collectLastValue(repository.reactivatedId)
-            val sortedMedia by collectLastValue(repository.sortedMedia)
+            val currentMedia by collectLastValue(repository.currentMedia)
             // WHEN we have media that was recently played, but not currently active
             val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
             val controlCommonModel =
@@ -762,7 +761,7 @@
 
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
 
-            assertThat(sortedMedia?.values).containsExactly(controlCommonModel)
+            assertThat(currentMedia).containsExactly(controlCommonModel)
             verify(listener)
                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
 
@@ -790,7 +789,7 @@
                 )
                 .isTrue()
             // Smartspace update should also be propagated but not prioritized.
-            assertThat(sortedMedia?.values).containsExactly(controlCommonModel, recsCommonModel)
+            assertThat(currentMedia).containsExactly(controlCommonModel, recsCommonModel)
             verify(listener)
                 .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
             verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
@@ -803,13 +802,13 @@
             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
             val reactivatedKey by collectLastValue(repository.reactivatedId)
-            val sortedMedia by collectLastValue(repository.sortedMedia)
+            val currentMedia by collectLastValue(repository.currentMedia)
 
             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
             mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
 
             verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
-            assertThat(sortedMedia?.values).isEmpty()
+            assertThat(currentMedia).isEmpty()
             assertThat(
                     hasActiveMediaOrRecommendation(
                         selectedUserEntries,
@@ -827,7 +826,7 @@
             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
             val reactivatedKey by collectLastValue(repository.reactivatedId)
-            val sortedMedia by collectLastValue(repository.sortedMedia)
+            val currentMedia by collectLastValue(repository.currentMedia)
             val controlCommonModel =
                 MediaCommonModel.MediaControl(
                     MediaDataLoadingModel.Loaded(dataMain.instanceId),
@@ -836,7 +835,7 @@
             val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
             mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
 
-            assertThat(sortedMedia?.values).containsExactly(controlCommonModel)
+            assertThat(currentMedia).containsExactly(controlCommonModel)
             verify(listener)
                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
 
@@ -857,7 +856,7 @@
             mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
 
             verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
-            assertThat(sortedMedia?.values).containsExactly(controlCommonModel)
+            assertThat(currentMedia).containsExactly(controlCommonModel)
             assertThat(
                     hasActiveMediaOrRecommendation(
                         selectedUserEntries,
@@ -875,7 +874,7 @@
             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
             val reactivatedKey by collectLastValue(repository.reactivatedId)
-            val sortedMedia by collectLastValue(repository.sortedMedia)
+            val currentMedia by collectLastValue(repository.currentMedia)
             val recsCommonModel =
                 MediaCommonModel.MediaRecommendations(
                     SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
@@ -887,7 +886,7 @@
 
             verify(listener)
                 .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
-            assertThat(sortedMedia?.values).containsExactly(recsCommonModel)
+            assertThat(currentMedia).containsExactly(recsCommonModel)
             assertThat(
                     hasActiveMediaOrRecommendation(
                         selectedUserEntries,
@@ -906,7 +905,7 @@
             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
             val reactivatedKey by collectLastValue(repository.reactivatedId)
-            val sortedMedia by collectLastValue(repository.sortedMedia)
+            val currentMedia by collectLastValue(repository.currentMedia)
             val recsCommonModel =
                 MediaCommonModel.MediaRecommendations(
                     SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
@@ -926,7 +925,7 @@
 
             verify(listener)
                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
-            assertThat(sortedMedia?.values).containsExactly(controlCommonModel)
+            assertThat(currentMedia).containsExactly(controlCommonModel)
 
             // And an inactive recommendation is loaded
             mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
@@ -936,7 +935,7 @@
                 .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
             verify(listener, never())
                 .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
-            assertThat(sortedMedia?.values).containsExactly(controlCommonModel, recsCommonModel)
+            assertThat(currentMedia).containsExactly(controlCommonModel, recsCommonModel)
             assertThat(
                     hasActiveMediaOrRecommendation(
                         selectedUserEntries,
@@ -974,7 +973,7 @@
             val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
             val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
             val reactivatedKey by collectLastValue(repository.reactivatedId)
-            val sortedMedia by collectLastValue(repository.sortedMedia)
+            val currentMedia by collectLastValue(repository.currentMedia)
             val recsCommonModel =
                 MediaCommonModel.MediaRecommendations(
                     SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
@@ -990,7 +989,7 @@
 
             verify(listener)
                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
-            assertThat(sortedMedia?.values).containsExactly(controlCommonModel)
+            assertThat(currentMedia).containsExactly(controlCommonModel)
 
             // AND we get a smartspace signal with extra to trigger resume
             runCurrent()
@@ -1009,7 +1008,7 @@
                     eq(100),
                     eq(true)
                 )
-            assertThat(sortedMedia?.values).containsExactly(controlCommonModel, recsCommonModel)
+            assertThat(currentMedia).containsExactly(controlCommonModel, recsCommonModel)
             assertThat(
                     hasActiveMediaOrRecommendation(
                         selectedUserEntries,
@@ -1026,7 +1025,7 @@
     @Test
     fun smartspaceLoaded_notShouldTriggerResume_doesNotTrigger() =
         testScope.runTest {
-            val sortedMedia by collectLastValue(repository.sortedMedia)
+            val currentMedia by collectLastValue(repository.currentMedia)
             val recsCommonModel =
                 MediaCommonModel.MediaRecommendations(
                     SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
@@ -1043,7 +1042,7 @@
 
             verify(listener)
                 .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
-            assertThat(sortedMedia?.values).containsExactly(controlCommonModel)
+            assertThat(currentMedia).containsExactly(controlCommonModel)
 
             // AND we get a smartspace signal with extra to not trigger resume
             val extras = Bundle().apply { putBoolean(EXTRA_KEY_TRIGGER_RESUME, false) }
@@ -1056,7 +1055,7 @@
             // But the smartspace update is still propagated
             verify(listener)
                 .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
-            assertThat(sortedMedia?.values).containsExactly(controlCommonModel, recsCommonModel)
+            assertThat(currentMedia).containsExactly(controlCommonModel, recsCommonModel)
         }
 
     private fun hasActiveMediaOrRecommendation(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 1de7ee3..3bf4173 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -71,10 +71,6 @@
 import com.android.systemui.statusbar.SbnBuilder
 import com.android.systemui.testKosmos
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.utils.os.FakeHandler
@@ -101,6 +97,10 @@
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.MockitoSession
 import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
+import org.mockito.kotlin.capture
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
 import org.mockito.quality.Strictness
 
 private const val KEY = "KEY"
@@ -384,7 +384,7 @@
         // THEN it is removed and listeners are informed
         foregroundExecutor.advanceClockToLast()
         foregroundExecutor.runAllReady()
-        verify(listener).onMediaDataRemoved(PACKAGE_NAME)
+        verify(listener).onMediaDataRemoved(PACKAGE_NAME, false)
     }
 
     @Test
@@ -567,7 +567,7 @@
         addNotificationAndLoad()
         val data = mediaDataCaptor.value
         mediaDataProcessor.onNotificationRemoved(KEY)
-        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
         verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
     }
 
@@ -812,7 +812,7 @@
                 eq(false)
             )
         assertThat(mediaDataCaptor.value.resumption).isTrue()
-        verify(listener, never()).onMediaDataRemoved(eq(KEY))
+        verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
         // WHEN the second is removed
         mediaDataProcessor.onNotificationRemoved(KEY_2)
         // THEN the data is for resumption and the second key is removed
@@ -826,7 +826,7 @@
                 eq(false)
             )
         assertThat(mediaDataCaptor.value.resumption).isTrue()
-        verify(listener).onMediaDataRemoved(eq(KEY_2))
+        verify(listener).onMediaDataRemoved(eq(KEY_2), eq(false))
     }
 
     @Test
@@ -851,7 +851,7 @@
         mediaDataProcessor.onNotificationRemoved(KEY)
 
         // THEN the media data is removed
-        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
     }
 
     @Test
@@ -901,7 +901,7 @@
         mediaDataProcessor.onNotificationRemoved(KEY)
 
         // THEN the media data is removed
-        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
     }
 
     @Test
@@ -940,7 +940,7 @@
         assertThat(mediaDataCaptor.value.isPlaying).isFalse()
 
         // And the oldest resume control was removed
-        verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"))
+        verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"), eq(false))
     }
 
     fun testOnNotificationRemoved_lockDownMode() {
@@ -950,7 +950,7 @@
         val data = mediaDataCaptor.value
         mediaDataProcessor.onNotificationRemoved(KEY)
 
-        verify(listener, never()).onMediaDataRemoved(eq(KEY))
+        verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false))
         verify(logger, never())
             .logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
         verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
@@ -1183,7 +1183,7 @@
         mediaDataProcessor.setMediaResumptionEnabled(false)
 
         // THEN the resume controls are dismissed
-        verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME))
+        verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME), eq(false))
         verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
     }
 
@@ -1191,19 +1191,19 @@
     fun testDismissMedia_listenerCalled() {
         addNotificationAndLoad()
         val data = mediaDataCaptor.value
-        val removed = mediaDataProcessor.dismissMediaData(KEY, 0L)
+        val removed = mediaDataProcessor.dismissMediaData(KEY, 0L, true)
         assertThat(removed).isTrue()
 
         foregroundExecutor.advanceClockToLast()
         foregroundExecutor.runAllReady()
 
-        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(listener).onMediaDataRemoved(eq(KEY), eq(true))
         verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
     }
 
     @Test
     fun testDismissMedia_keyDoesNotExist_returnsFalse() {
-        val removed = mediaDataProcessor.dismissMediaData(KEY, 0L)
+        val removed = mediaDataProcessor.dismissMediaData(KEY, 0L, true)
         assertThat(removed).isFalse()
     }
 
@@ -2102,7 +2102,7 @@
         sessionCallbackCaptor.value.invoke(KEY)
 
         // It remains as a regular player
-        verify(listener, never()).onMediaDataRemoved(eq(KEY))
+        verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
         verify(listener, never())
             .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
     }
@@ -2118,7 +2118,7 @@
         mediaDataProcessor.onNotificationRemoved(KEY)
 
         // It is fully removed
-        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
         verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
         verify(listener, never())
             .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2171,7 +2171,7 @@
         mediaDataProcessor.onNotificationRemoved(KEY)
 
         // It remains as a regular player
-        verify(listener, never()).onMediaDataRemoved(eq(KEY))
+        verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
         verify(listener, never())
             .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
     }
@@ -2224,7 +2224,7 @@
         sessionCallbackCaptor.value.invoke(KEY)
 
         // It is fully removed
-        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
         verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
         verify(listener, never())
             .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2278,7 +2278,7 @@
         sessionCallbackCaptor.value.invoke(KEY)
 
         // It is fully removed.
-        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
         verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
         verify(listener, never())
             .onMediaDataLoaded(
@@ -2304,7 +2304,7 @@
         sessionCallbackCaptor.value.invoke(KEY)
 
         // It is fully removed
-        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
         verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
         verify(listener, never())
             .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2354,7 +2354,7 @@
         mediaDataProcessor.onNotificationRemoved(KEY)
 
         // We still make sure to remove it
-        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
index a447e44..d2701dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
@@ -51,7 +51,6 @@
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
@@ -60,6 +59,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.any
@@ -71,6 +71,7 @@
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.eq
 
 private const val KEY = "TEST_KEY"
 private const val KEY_OLD = "TEST_KEY_OLD"
@@ -158,7 +159,8 @@
 
     @Test
     fun removeUnknown() {
-        manager.onMediaDataRemoved("unknown")
+        manager.onMediaDataRemoved("unknown", false)
+        verify(listener, never()).onKeyRemoved(eq(KEY), anyBoolean())
     }
 
     @Test
@@ -170,7 +172,7 @@
     @Test
     fun loadAndRemoveMediaData() {
         manager.onMediaDataLoaded(KEY, null, mediaData)
-        manager.onMediaDataRemoved(KEY)
+        manager.onMediaDataRemoved(KEY, false)
         fakeBgExecutor.runAllReady()
         verify(lmm).unregisterCallback(any())
         verify(muteAwaitManager).stopListening()
@@ -386,9 +388,9 @@
     fun listenerReceivesKeyRemoved() {
         manager.onMediaDataLoaded(KEY, null, mediaData)
         // WHEN the notification is removed
-        manager.onMediaDataRemoved(KEY)
+        manager.onMediaDataRemoved(KEY, true)
         // THEN the listener receives key removed event
-        verify(listener).onKeyRemoved(eq(KEY))
+        verify(listener).onKeyRemoved(eq(KEY), eq(true))
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt
index 5a3c220..31a2435 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.media.controls.MediaTestUtils
 import com.android.systemui.media.controls.shared.model.MediaData
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import org.junit.After
 import org.junit.Before
@@ -38,12 +37,13 @@
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
-import org.mockito.Mockito.any
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
 
 private const val PACKAGE = "PKG"
 private const val KEY = "TEST_KEY"
@@ -165,10 +165,10 @@
 
     @Test
     fun noMediaSession_removedEventNotFiltered() {
-        filter.onMediaDataRemoved(KEY)
+        filter.onMediaDataRemoved(KEY, false)
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
-        verify(mediaListener).onMediaDataRemoved(eq(KEY))
+        verify(mediaListener).onMediaDataRemoved(eq(KEY), eq(false))
     }
 
     @Test
@@ -193,11 +193,11 @@
         whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
         sessionListener.onActiveSessionsChanged(controllers)
         // WHEN a removed event is received
-        filter.onMediaDataRemoved(KEY)
+        filter.onMediaDataRemoved(KEY, false)
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the event is not filtered
-        verify(mediaListener).onMediaDataRemoved(eq(KEY))
+        verify(mediaListener).onMediaDataRemoved(eq(KEY), eq(false))
     }
 
     @Test
@@ -294,7 +294,7 @@
                 anyBoolean()
             )
         // AND there should be a removed event for key2
-        verify(mediaListener).onMediaDataRemoved(eq(key2))
+        verify(mediaListener).onMediaDataRemoved(eq(key2), eq(false))
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
index 3cc65c9..6ca0bef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
@@ -31,10 +31,6 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -52,6 +48,10 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
+import org.mockito.kotlin.capture
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
 
 private const val KEY = "KEY"
 private const val PACKAGE = "PKG"
@@ -166,12 +166,12 @@
     @Test
     fun testOnMediaDataRemoved_unregistersPlaybackListener() {
         mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
-        mediaTimeoutListener.onMediaDataRemoved(KEY)
+        mediaTimeoutListener.onMediaDataRemoved(KEY, false)
         verify(mediaController).unregisterCallback(anyObject())
 
         // Ignores duplicate requests
         clearInvocations(mediaController)
-        mediaTimeoutListener.onMediaDataRemoved(KEY)
+        mediaTimeoutListener.onMediaDataRemoved(KEY, false)
         verify(mediaController, never()).unregisterCallback(anyObject())
     }
 
@@ -181,7 +181,7 @@
         mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
         assertThat(executor.numPending()).isEqualTo(1)
         // WHEN the media is removed
-        mediaTimeoutListener.onMediaDataRemoved(KEY)
+        mediaTimeoutListener.onMediaDataRemoved(KEY, false)
         // THEN the timeout runnable is cancelled
         assertThat(executor.numPending()).isEqualTo(0)
     }
@@ -398,7 +398,7 @@
         // WHEN we have a resume control
         testOnMediaDataLoaded_resumption_registersTimeout()
         // AND the media is removed
-        mediaTimeoutListener.onMediaDataRemoved(PACKAGE)
+        mediaTimeoutListener.onMediaDataRemoved(PACKAGE, false)
 
         // THEN the timeout runnable is cancelled
         assertThat(executor.numPending()).isEqualTo(0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index 0a5aace..7856f9b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -33,6 +33,10 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -52,15 +56,16 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.PageIndicator
 import com.android.systemui.res.R
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.setSceneTransition
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.testKosmos
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.settings.GlobalSettings
 import com.android.systemui.util.settings.SecureSettings
@@ -74,12 +79,14 @@
 import kotlinx.coroutines.test.TestDispatcher
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito.anyLong
 import org.mockito.Mockito.floatThat
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
@@ -88,6 +95,9 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.capture
+import org.mockito.kotlin.eq
 
 private val DATA = MediaTestUtils.emptyMediaData
 
@@ -136,6 +146,9 @@
     private lateinit var testDispatcher: TestDispatcher
     private lateinit var mediaCarouselController: MediaCarouselController
 
+    private var originalResumeSetting =
+        Settings.Secure.getInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 1)
+
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
@@ -145,29 +158,30 @@
         testDispatcher = UnconfinedTestDispatcher()
         mediaCarouselController =
             MediaCarouselController(
-                context,
-                mediaControlPanelFactory,
-                visualStabilityProvider,
-                mediaHostStatesManager,
-                activityStarter,
-                clock,
-                kosmos.testDispatcher,
-                executor,
-                bgExecutor,
-                testDispatcher,
-                mediaDataManager,
-                configurationController,
-                falsingManager,
-                dumpManager,
-                logger,
-                debugLogger,
-                mediaFlags,
-                keyguardUpdateMonitor,
-                kosmos.keyguardTransitionInteractor,
-                globalSettings,
-                secureSettings,
-                kosmos.mediaCarouselViewModel,
-                mediaViewControllerFactory,
+                context = context,
+                mediaControlPanelFactory = mediaControlPanelFactory,
+                visualStabilityProvider = visualStabilityProvider,
+                mediaHostStatesManager = mediaHostStatesManager,
+                activityStarter = activityStarter,
+                systemClock = clock,
+                mainDispatcher = kosmos.testDispatcher,
+                executor = executor,
+                bgExecutor = bgExecutor,
+                backgroundDispatcher = testDispatcher,
+                mediaManager = mediaDataManager,
+                configurationController = configurationController,
+                falsingManager = falsingManager,
+                dumpManager = dumpManager,
+                logger = logger,
+                debugLogger = debugLogger,
+                mediaFlags = mediaFlags,
+                keyguardUpdateMonitor = keyguardUpdateMonitor,
+                keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+                globalSettings = globalSettings,
+                secureSettings = secureSettings,
+                mediaCarouselViewModel = kosmos.mediaCarouselViewModel,
+                mediaViewControllerFactory = mediaViewControllerFactory,
+                sceneInteractor = kosmos.sceneInteractor,
             )
         verify(configurationController).addCallback(capture(configListener))
         verify(mediaDataManager).addListener(capture(listener))
@@ -186,6 +200,15 @@
             )
     }
 
+    @After
+    fun tearDown() {
+        Settings.Secure.putInt(
+            context.contentResolver,
+            Settings.Secure.MEDIA_CONTROLS_RESUME,
+            originalResumeSetting
+        )
+    }
+
     @Test
     fun testPlayerOrdering() {
         // Test values: key, data, last active time
@@ -818,10 +841,12 @@
         verify(mediaCarousel).visibility = View.VISIBLE
     }
 
+    @DisableSceneContainer
     @ExperimentalCoroutinesApi
     @Test
     fun testKeyguardGone_showMediaCarousel() =
         kosmos.testScope.runTest {
+            kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
             var updatedVisibility = false
             mediaCarouselController.updateHostVisibility = { updatedVisibility = true }
             mediaCarouselController.mediaCarousel = mediaCarousel
@@ -840,10 +865,30 @@
             job.cancel()
         }
 
+    @EnableSceneContainer
+    @ExperimentalCoroutinesApi
+    @Test
+    fun testKeyguardGone_showMediaCarousel_scene_container() =
+        kosmos.testScope.runTest {
+            kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
+            var updatedVisibility = false
+            mediaCarouselController.updateHostVisibility = { updatedVisibility = true }
+            mediaCarouselController.mediaCarousel = mediaCarousel
+
+            val job = mediaCarouselController.listenForAnyStateToGoneKeyguardTransition(this)
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+            verify(mediaCarousel).visibility = View.VISIBLE
+            assertEquals(true, updatedVisibility)
+
+            job.cancel()
+        }
+
     @ExperimentalCoroutinesApi
     @Test
     fun keyguardShowing_notAllowedOnLockscreen_updateVisibility() {
         kosmos.testScope.runTest {
+            kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
             var updatedVisibility = false
             mediaCarouselController.updateHostVisibility = { updatedVisibility = true }
             mediaCarouselController.mediaCarousel = mediaCarousel
@@ -870,6 +915,7 @@
     @Test
     fun keyguardShowing_allowedOnLockscreen_updateVisibility() {
         kosmos.testScope.runTest {
+            kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
             var updatedVisibility = false
             mediaCarouselController.updateHostVisibility = { updatedVisibility = true }
             mediaCarouselController.mediaCarousel = mediaCarousel
@@ -968,6 +1014,45 @@
         verify(panel).updateAnimatorDurationScale()
     }
 
+    @Test
+    fun swipeToDismiss_pausedAndResumeOff_userInitiated() {
+        // When resumption is disabled, paused media should be dismissed after being swiped away
+        Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
+
+        val pausedMedia = DATA.copy(isPlaying = false)
+        listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia)
+        mediaCarouselController.onSwipeToDismiss()
+
+        // When it can be removed immediately on update
+        whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(true)
+        val inactiveMedia = pausedMedia.copy(active = false)
+        listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, inactiveMedia)
+
+        // This is processed as a user-initiated dismissal
+        verify(debugLogger).logMediaRemoved(eq(PAUSED_LOCAL), eq(true))
+        verify(mediaDataManager).dismissMediaData(eq(PAUSED_LOCAL), anyLong(), eq(true))
+    }
+
+    @Test
+    fun swipeToDismiss_pausedAndResumeOff_delayed_userInitiated() {
+        // When resumption is disabled, paused media should be dismissed after being swiped away
+        Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
+        mediaCarouselController.updateHostVisibility = {}
+
+        val pausedMedia = DATA.copy(isPlaying = false)
+        listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia)
+        mediaCarouselController.onSwipeToDismiss()
+
+        // When it can't be removed immediately on update
+        whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(false)
+        val inactiveMedia = pausedMedia.copy(active = false)
+        listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, inactiveMedia)
+        visualStabilityCallback.value.onReorderingAllowed()
+
+        // This is processed as a user-initiated dismissal
+        verify(mediaDataManager).dismissMediaData(eq(PAUSED_LOCAL), anyLong(), eq(true))
+    }
+
     /**
      * Helper method when a configuration change occurs.
      *
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index 83e4d31..6d7976e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -98,11 +98,6 @@
 import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
 import com.android.systemui.util.animation.TransitionLayout
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.KotlinArgumentCaptor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.withArgCaptor
 import com.android.systemui.util.settings.GlobalSettings
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -125,6 +120,9 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
 
 private const val KEY = "TEST_KEY"
 private const val PACKAGE = "PKG"
@@ -247,8 +245,7 @@
         // Set up package manager mocks
         val icon = context.getDrawable(R.drawable.ic_android)
         whenever(packageManager.getApplicationIcon(anyString())).thenReturn(icon)
-        whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java)))
-            .thenReturn(icon)
+        whenever(packageManager.getApplicationIcon(any<ApplicationInfo>())).thenReturn(icon)
         whenever(packageManager.getApplicationInfo(eq(PACKAGE), anyInt()))
             .thenReturn(applicationInfo)
         whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE)
@@ -644,7 +641,7 @@
         bgExecutor.runAllReady()
         mainExecutor.runAllReady()
 
-        verify(albumView).setImageDrawable(any(Drawable::class.java))
+        verify(albumView).setImageDrawable(any<Drawable>())
     }
 
     @Test
@@ -657,7 +654,7 @@
         bgExecutor.runAllReady()
         mainExecutor.runAllReady()
 
-        verify(albumView).setImageDrawable(any(Drawable::class.java))
+        verify(albumView).setImageDrawable(any<Drawable>())
     }
 
     @Test
@@ -675,12 +672,12 @@
         player.bindPlayer(state0, PACKAGE)
         bgExecutor.runAllReady()
         mainExecutor.runAllReady()
-        verify(albumView).setImageDrawable(any(Drawable::class.java))
+        verify(albumView).setImageDrawable(any<Drawable>())
 
         // Run Metadata update so that later states don't update
         val captor = argumentCaptor<Animator.AnimatorListener>()
         verify(mockAnimator, times(2)).addListener(captor.capture())
-        captor.value.onAnimationEnd(mockAnimator)
+        captor.lastValue.onAnimationEnd(mockAnimator)
         assertThat(titleText.getText()).isEqualTo(TITLE)
         assertThat(artistText.getText()).isEqualTo(ARTIST)
 
@@ -696,13 +693,13 @@
         player.bindPlayer(state2, PACKAGE)
         bgExecutor.runAllReady()
         mainExecutor.runAllReady()
-        verify(albumView, times(2)).setImageDrawable(any(Drawable::class.java))
+        verify(albumView, times(2)).setImageDrawable(any<Drawable>())
 
         // Fourth binding to new image runs transition due to color scheme change
         player.bindPlayer(state3, PACKAGE)
         bgExecutor.runAllReady()
         mainExecutor.runAllReady()
-        verify(albumView, times(3)).setImageDrawable(any(Drawable::class.java))
+        verify(albumView, times(3)).setImageDrawable(any<Drawable>())
     }
 
     @Test
@@ -974,7 +971,7 @@
 
         val captor = argumentCaptor<SeekBarObserver>()
         verify(seekBarData).observeForever(captor.capture())
-        val seekBarObserver = captor.value!!
+        val seekBarObserver = captor.lastValue
 
         // Then the seekbar is set to animate
         assertThat(seekBarObserver.animationEnabled).isTrue()
@@ -1086,27 +1083,19 @@
         whenever(mockAvd0.isRunning()).thenReturn(false)
         val captor = ArgumentCaptor.forClass(Animatable2.AnimationCallback::class.java)
         verify(mockAvd0, times(1)).registerAnimationCallback(captor.capture())
-        verify(mockAvd1, never())
-            .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java))
-        verify(mockAvd2, never())
-            .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java))
+        verify(mockAvd1, never()).registerAnimationCallback(any<Animatable2.AnimationCallback>())
+        verify(mockAvd2, never()).registerAnimationCallback(any<Animatable2.AnimationCallback>())
         captor.getValue().onAnimationEnd(mockAvd0)
 
         // Validate correct state was bound
         assertThat(actionPlayPause.contentDescription).isEqualTo("loading")
         assertThat(actionPlayPause.getBackground()).isNull()
-        verify(mockAvd0, times(1))
-            .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java))
-        verify(mockAvd1, times(1))
-            .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java))
-        verify(mockAvd2, times(1))
-            .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java))
-        verify(mockAvd0, times(1))
-            .unregisterAnimationCallback(any(Animatable2.AnimationCallback::class.java))
-        verify(mockAvd1, times(1))
-            .unregisterAnimationCallback(any(Animatable2.AnimationCallback::class.java))
-        verify(mockAvd2, never())
-            .unregisterAnimationCallback(any(Animatable2.AnimationCallback::class.java))
+        verify(mockAvd0, times(1)).registerAnimationCallback(any<Animatable2.AnimationCallback>())
+        verify(mockAvd1, times(1)).registerAnimationCallback(any<Animatable2.AnimationCallback>())
+        verify(mockAvd2, times(1)).registerAnimationCallback(any<Animatable2.AnimationCallback>())
+        verify(mockAvd0, times(1)).unregisterAnimationCallback(any<Animatable2.AnimationCallback>())
+        verify(mockAvd1, times(1)).unregisterAnimationCallback(any<Animatable2.AnimationCallback>())
+        verify(mockAvd2, never()).unregisterAnimationCallback(any<Animatable2.AnimationCallback>())
     }
 
     @Test
@@ -1118,7 +1107,7 @@
         // Capture animation handler
         val captor = argumentCaptor<Animator.AnimatorListener>()
         verify(mockAnimator, times(2)).addListener(captor.capture())
-        val handler = captor.value
+        val handler = captor.lastValue
 
         // Validate text views unchanged but animation started
         assertThat(titleText.getText()).isEqualTo("")
@@ -1147,7 +1136,7 @@
         // Capture animation handler
         val captor = argumentCaptor<Animator.AnimatorListener>()
         verify(mockAnimator, times(2)).addListener(captor.capture())
-        val handler = captor.value
+        val handler = captor.lastValue
 
         // Validate text views unchanged but animation started
         assertThat(titleText.getText()).isEqualTo("")
@@ -1179,7 +1168,7 @@
         // Capture animation handler
         val captor = argumentCaptor<Animator.AnimatorListener>()
         verify(mockAnimator, times(2)).addListener(captor.capture())
-        val handler = captor.value
+        val handler = captor.lastValue
 
         handler.onAnimationEnd(mockAnimator)
         assertThat(artistText.getText()).isEqualTo("ARTIST_0")
@@ -1344,7 +1333,7 @@
         assertThat(dismiss.isEnabled).isEqualTo(true)
         dismiss.callOnClick()
         verify(logger).logLongPressDismiss(anyInt(), eq(PACKAGE), eq(instanceId))
-        verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong())
+        verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong(), eq(true))
     }
 
     @Test
@@ -1360,7 +1349,8 @@
     @Test
     fun player_dismissButtonClick_notInManager() {
         val mediaKey = "key for dismissal"
-        whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong())).thenReturn(false)
+        whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong(), eq(true)))
+            .thenReturn(false)
 
         player.attachPlayer(viewHolder)
         val state = mediaData.copy(notificationKey = KEY)
@@ -1369,8 +1359,8 @@
         assertThat(dismiss.isEnabled).isEqualTo(true)
         dismiss.callOnClick()
 
-        verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong())
-        verify(mediaCarouselController).removePlayer(eq(mediaKey), eq(false), eq(false))
+        verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong(), eq(true))
+        verify(mediaCarouselController).removePlayer(eq(mediaKey), eq(false), eq(false), eq(true))
     }
 
     @Test
@@ -1774,10 +1764,9 @@
         player.attachPlayer(viewHolder)
         player.bindPlayer(mediaData, KEY)
 
-        val callback: () -> Unit = {}
-        val captor = KotlinArgumentCaptor(callback::class.java)
+        val captor = argumentCaptor<() -> Unit>()
         verify(seekBarViewModel).logSeek = captor.capture()
-        captor.value.invoke()
+        captor.lastValue.invoke()
 
         verify(logger).logSeek(anyInt(), eq(PACKAGE), eq(instanceId))
     }
@@ -1800,7 +1789,7 @@
         // THEN it sends the PendingIntent without dismissing keyguard first,
         // and does not use the Intent directly (see b/271845008)
         captor.value.onClick(viewHolder.player)
-        verify(pendingIntent).send(any(Bundle::class.java))
+        verify(pendingIntent).send(any<Bundle>())
         verify(pendingIntent, never()).getIntent()
         verify(activityStarter, never()).postStartActivityDismissingKeyguard(eq(clickIntent), any())
     }
@@ -2218,8 +2207,8 @@
         mainExecutor.runAllReady()
 
         verify(recCardTitle).setTextColor(any<Int>())
-        verify(recAppIconItem, times(3)).setImageDrawable(any(Drawable::class.java))
-        verify(coverItem, times(3)).setImageDrawable(any(Drawable::class.java))
+        verify(recAppIconItem, times(3)).setImageDrawable(any<Drawable>())
+        verify(coverItem, times(3)).setImageDrawable(any<Drawable>())
         verify(coverItem, times(3)).imageMatrix = any()
     }
 
@@ -2546,7 +2535,7 @@
         seamless.callOnClick()
 
         // Then we send the pending intent as is, without modifying the original intent
-        verify(pendingIntent).send(any(Bundle::class.java))
+        verify(pendingIntent).send(any<Bundle>())
         verify(pendingIntent, never()).getIntent()
     }
 
@@ -2578,13 +2567,16 @@
         return Icon.createWithBitmap(bmp)
     }
 
-    private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener =
-        withArgCaptor {
-            verify(seekBarViewModel).setScrubbingChangeListener(capture())
-        }
+    private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener {
+        val captor = argumentCaptor<SeekBarViewModel.ScrubbingChangeListener>()
+        verify(seekBarViewModel).setScrubbingChangeListener(captor.capture())
+        return captor.lastValue
+    }
 
-    private fun getEnabledChangeListener(): SeekBarViewModel.EnabledChangeListener = withArgCaptor {
-        verify(seekBarViewModel).setEnabledChangeListener(capture())
+    private fun getEnabledChangeListener(): SeekBarViewModel.EnabledChangeListener {
+        val captor = argumentCaptor<SeekBarViewModel.EnabledChangeListener>()
+        verify(seekBarViewModel).setEnabledChangeListener(captor.capture())
+        return captor.lastValue
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
index 3b6a88a..5dbfe47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
@@ -25,6 +25,8 @@
 import static org.mockito.Mockito.verify;
 
 import android.content.Intent;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.filters.SmallTest;
@@ -91,8 +93,8 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
     public void launchMediaOutputBroadcastDialog_flagOff_broadcastDialogFactoryNotCalled() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING);
         Intent intent = new Intent(
                 MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
         intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
@@ -105,8 +107,8 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
     public void launchMediaOutputBroadcastDialog_ExtraPackageName_BroadcastDialogFactoryCalled() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING);
         Intent intent = new Intent(
                 MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
         intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
@@ -119,8 +121,8 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
     public void launchMediaOutputBroadcastDialog_WrongExtraKey_DialogBroadcastFactoryNotCalled() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING);
         Intent intent = new Intent(
                 MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
         intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
@@ -133,8 +135,8 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
     public void launchMediaOutputBroadcastDialog_NoExtra_BroadcastDialogFactoryNotCalled() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING);
         Intent intent = new Intent(
                 MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
index ff7c970..8f8630e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
@@ -104,11 +104,11 @@
         listener.onMediaDataLoaded(mKey, mOldKey, mData, /* immediately= */true,
                 /* receivedSmartspaceCardLatency= */0, /* isSsReactived= */ false);
 
-        listener.onMediaDataRemoved(mKey);
+        listener.onMediaDataRemoved(mKey, false);
         verify(mDreamOverlayStateController, never()).removeComplication(any());
 
         when(mMediaDataManager.hasActiveMedia()).thenReturn(false);
-        listener.onMediaDataRemoved(mKey);
+        listener.onMediaDataRemoved(mKey, false);
         verify(mDreamOverlayStateController).removeComplication(eq(mMediaEntryComplication));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
index db275ec..db36131 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
@@ -52,7 +52,7 @@
             val taskId = 123
             val isLowResolution = false
             val snapshot = createTaskSnapshot()
-            val thumbnailData = ThumbnailData(snapshot)
+            val thumbnailData = ThumbnailData.fromSnapshot(snapshot)
             whenever(activityManager.getTaskThumbnail(taskId, isLowResolution))
                 .thenReturn(thumbnailData)
 
@@ -74,7 +74,7 @@
     fun captureThumbnail_thumbnailAvailable_returnsThumbnailData() =
         testScope.runTest {
             val taskId = 321
-            val thumbnailData = ThumbnailData(createTaskSnapshot())
+            val thumbnailData = ThumbnailData.fromSnapshot(createTaskSnapshot())
 
             whenever(activityManager.takeTaskThumbnail(taskId)).thenReturn(thumbnailData)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt
index 8e05410..c06a28e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt
@@ -42,13 +42,13 @@
     fun updateFlags() {
         underTest.updateFlags(
             Display.DEFAULT_DISPLAY,
-            1 to true,
-            2 to false,
-            3 to true,
+            1L to true,
+            2L to false,
+            3L to true,
         )
 
-        assertThat(underTest.flags and 1).isNotEqualTo(0)
-        assertThat(underTest.flags and 2).isEqualTo(0)
-        assertThat(underTest.flags and 3).isNotEqualTo(0)
+        assertThat(underTest.flags and 1L).isNotEqualTo(0L)
+        assertThat(underTest.flags and 2L).isEqualTo(0L)
+        assertThat(underTest.flags and 3L).isNotEqualTo(0L)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.kt
index 9f0e67b..85cc88d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.kt
@@ -15,11 +15,13 @@
  */
 package com.android.systemui.monet
 
-import androidx.test.filters.SmallTest
 import android.testing.AndroidTestingRunner
 import android.util.Log
+import android.util.Pair
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.theme.DynamicColors
+import com.google.ux.material.libmonet.dynamiccolor.DynamicColor
 import com.google.ux.material.libmonet.hct.Hct
 import com.google.ux.material.libmonet.scheme.SchemeTonalSpot
 import java.io.File
@@ -81,6 +83,10 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class ColorSchemeTest : SysuiTestCase() {
+    private val defaultContrast = 0.0
+    private val defaultIsDark = false
+    private val defaultIsFidelity = false
+
     @Test
     fun generateThemeStyles() {
         val document = buildDoc<Any>()
@@ -107,7 +113,7 @@
                 }
 
                 val style = document.createElement(styleValue.name.lowercase())
-                val colorScheme = ColorScheme(sourceColor.toInt(), false, styleValue)
+                val colorScheme = ColorScheme(sourceColor.toInt(), defaultIsDark, styleValue)
 
                 style.appendChild(
                     document.createTextNode(
@@ -139,7 +145,7 @@
         document.appendWithBreak(resources)
 
         // shade colors
-        val colorScheme = ColorScheme(GOOGLE_BLUE, false)
+        val colorScheme = ColorScheme(GOOGLE_BLUE, defaultIsDark)
         arrayOf(
                 Triple("accent1", "Primary", colorScheme.accent1),
                 Triple("accent2", "Secondary", colorScheme.accent2),
@@ -162,24 +168,35 @@
 
         resources.appendWithBreak(document.createComment(commentRoles), 2)
 
-        // dynamic colors
-        arrayOf(false, true).forEach { isDark ->
-            val suffix = if (isDark) "_dark" else "_light"
-            val dynamicScheme = SchemeTonalSpot(Hct.fromInt(GOOGLE_BLUE), isDark, 0.5)
-            DynamicColors.allDynamicColorsMapped(false).forEach {
-                resources.createColorEntry(
-                    "system_${it.first}$suffix",
-                    it.second.getArgb(dynamicScheme)
-                )
+        fun generateDynamic(pairs: List<Pair<String, DynamicColor>>) {
+            arrayOf(false, true).forEach { isDark ->
+                val suffix = if (isDark) "_dark" else "_light"
+                val dynamicScheme =
+                    SchemeTonalSpot(Hct.fromInt(GOOGLE_BLUE), isDark, defaultContrast)
+                pairs.forEach {
+                    resources.createColorEntry(
+                        "system_${it.first}$suffix",
+                        it.second.getArgb(dynamicScheme)
+                    )
+                }
             }
         }
 
+        // dynamic colors
+        generateDynamic(DynamicColors.allDynamicColorsMapped(defaultIsFidelity))
+
         // fixed colors
-        val dynamicScheme = SchemeTonalSpot(Hct.fromInt(GOOGLE_BLUE), false, 0.5)
-        DynamicColors.getFixedColorsMapped(false).forEach {
+        val dynamicScheme =
+            SchemeTonalSpot(Hct.fromInt(GOOGLE_BLUE), defaultIsDark, defaultContrast)
+        DynamicColors.getFixedColorsMapped(defaultIsFidelity).forEach {
             resources.createColorEntry("system_${it.first}", it.second.getArgb(dynamicScheme))
         }
 
+        resources.appendWithBreak(document.createComment(commentRoles), 2)
+
+        // custom colors
+        generateDynamic(DynamicColors.getCustomColorsMapped(defaultIsFidelity))
+
         saveFile(document, "role_values.xml")
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index 224e755..2ff660f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -125,7 +125,7 @@
     private AccessibilityManager.AccessibilityServicesStateChangeListener
             mAccessibilityServicesStateChangeListener;
 
-    private static final int ACCESSIBILITY_BUTTON_CLICKABLE_STATE =
+    private static final long ACCESSIBILITY_BUTTON_CLICKABLE_STATE =
             SYSUI_STATE_A11Y_BUTTON_CLICKABLE | SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
     private NavBarHelper mNavBarHelper;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 0e7a215..6cea1e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -38,7 +38,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
@@ -300,7 +300,7 @@
         doNothing().when(mWindowManager).addView(any(), any());
         doNothing().when(mWindowManager).removeViewImmediate(any());
         mMockSysUiState = mock(SysUiState.class);
-        when(mMockSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mMockSysUiState);
+        when(mMockSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mMockSysUiState);
 
         mContext.addMockSystemService(WindowManager.class, mWindowManager);
         mSysuiTestableContextExternal.addMockSystemService(WindowManager.class, mWindowManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
index 8d01e80d..bba275e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
@@ -16,18 +16,18 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.wm.shell.back.BackAnimation
 import com.android.wm.shell.pip.Pip
+import java.util.Optional
 import org.junit.Before
 import org.junit.Test
 import org.mockito.ArgumentMatchers
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.any
 import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
-import java.util.Optional
 
 @SmallTest
 class TaskbarDelegateTest : SysuiTestCase() {
@@ -74,7 +74,7 @@
         `when`(mNavBarHelper.edgeBackGestureHandler).thenReturn(mEdgeBackGestureHandler)
         `when`(mLightBarControllerFactory.create(any())).thenReturn(mLightBarTransitionController)
         `when`(mNavBarHelper.currentSysuiState).thenReturn(mCurrentSysUiState)
-        `when`(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState)
+        `when`(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState)
         mTaskStackChangeListeners = TaskStackChangeListeners.getTestInstance()
         mTaskbarDelegate = TaskbarDelegate(context, mLightBarControllerFactory,
             mStatusBarKeyguardViewManager)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index 890e1e0..0998c0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -94,6 +94,8 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.StatusBarNotification;
 import android.service.notification.ZenModeConfig;
@@ -1576,17 +1578,19 @@
     }
 
     @Test
+    @DisableFlags({
+        android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS,
+        android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL
+    })
     public void testUpdateGeneratedPreview_flagDisabled() {
-        mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
-        mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
         mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
         verify(mAppWidgetManager, times(0)).setWidgetPreview(any(), anyInt(), any());
     }
 
     @Test
+    @EnableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS)
+    @DisableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL)
     public void testUpdateGeneratedPreview_userLocked() {
-        mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
-        mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
         when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(false);
 
         mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
@@ -1594,9 +1598,9 @@
     }
 
     @Test
+    @EnableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS)
+    @DisableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL)
     public void testUpdateGeneratedPreview_userUnlocked() {
-        mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
-        mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
         when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true);
         when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true);
 
@@ -1605,9 +1609,9 @@
     }
 
     @Test
+    @EnableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS)
+    @DisableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL)
     public void testUpdateGeneratedPreview_doesNotSetTwice() {
-        mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
-        mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
         when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true);
         when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true);
 
@@ -1617,9 +1621,11 @@
     }
 
     @Test
+    @EnableFlags({
+        android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS,
+        android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL
+    })
     public void testUpdateGeneratedPreviewWithDataParcel_userLocked() throws InterruptedException {
-        mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
-        mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
         when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(false);
 
         mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
@@ -1628,10 +1634,12 @@
     }
 
     @Test
+    @EnableFlags({
+        android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS,
+        android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL
+    })
     public void testUpdateGeneratedPreviewWithDataParcel_userUnlocked()
             throws InterruptedException {
-        mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
-        mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
         when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true);
         when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true);
 
@@ -1641,10 +1649,12 @@
     }
 
     @Test
+    @EnableFlags({
+        android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS,
+        android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL
+    })
     public void testUpdateGeneratedPreviewWithDataParcel_doesNotSetTwice()
             throws InterruptedException {
-        mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
-        mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
         when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true);
         when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt
index 629c663..bc947fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt
@@ -3,6 +3,7 @@
 import android.content.ComponentName
 import android.service.quicksettings.Tile
 import android.testing.AndroidTestingRunner
+import android.widget.Switch
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.qs.QSTile
@@ -66,15 +67,19 @@
         assertThat(proto?.hasBooleanState()).isFalse()
     }
 
+    /**
+     * The [QSTile.AdapterState.expandedAccessibilityClassName] setting to [Switch] results in the
+     * proto having a booleanState. The value of that boolean is true iff the tile is active.
+     */
     @Test
-    fun booleanState_ACTIVE() {
+    fun adapterState_ACTIVE() {
         val state =
-            QSTile.BooleanState().apply {
+            QSTile.AdapterState().apply {
                 spec = TEST_SPEC
                 label = TEST_LABEL
                 secondaryLabel = TEST_SUBTITLE
                 state = Tile.STATE_ACTIVE
-                value = true
+                expandedAccessibilityClassName = Switch::class.java.name
             }
         val proto = state.toProto()
 
@@ -89,6 +94,33 @@
         assertThat(proto?.booleanState).isTrue()
     }
 
+    /**
+     * Similar to [adapterState_ACTIVE], the use of
+     * [QSTile.AdapterState.expandedAccessibilityClassName] signals that the tile is toggleable.
+     */
+    @Test
+    fun adapterState_INACTIVE() {
+        val state =
+            QSTile.AdapterState().apply {
+                spec = TEST_SPEC
+                label = TEST_LABEL
+                secondaryLabel = TEST_SUBTITLE
+                state = Tile.STATE_INACTIVE
+                expandedAccessibilityClassName = Switch::class.java.name
+            }
+        val proto = state.toProto()
+
+        assertThat(proto).isNotNull()
+        assertThat(proto?.hasSpec()).isTrue()
+        assertThat(proto?.spec).isEqualTo(TEST_SPEC)
+        assertThat(proto?.hasComponentName()).isFalse()
+        assertThat(proto?.label).isEqualTo(TEST_LABEL)
+        assertThat(proto?.secondaryLabel).isEqualTo(TEST_SUBTITLE)
+        assertThat(proto?.state).isEqualTo(Tile.STATE_INACTIVE)
+        assertThat(proto?.hasBooleanState()).isTrue()
+        assertThat(proto?.booleanState).isFalse()
+    }
+
     @Test
     fun noSpec_returnsNull() {
         val state =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index f57f040..68307b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -16,10 +16,12 @@
 package com.android.systemui.qs.external;
 
 import static android.os.PowerExemptionManager.REASON_TILE_ONCLICK;
+import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf;
 import static android.service.quicksettings.TileService.START_ACTIVITY_NEEDS_PENDING_INTENT;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX;
 
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
@@ -55,13 +57,15 @@
 import android.os.HandlerThread;
 import android.os.IDeviceIdleController;
 import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
 import android.service.quicksettings.IQSService;
 import android.service.quicksettings.IQSTileService;
 import android.service.quicksettings.TileService;
 
 import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -73,12 +77,24 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
 import org.mockito.MockitoSession;
 
+import java.util.List;
+
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
 public class TileLifecycleManagerTest extends SysuiTestCase {
 
+    @Parameters(name = "{0}")
+    public static List<FlagsParameterization> getParams() {
+        return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX);
+    }
+
     private final PackageManagerAdapter mMockPackageManagerAdapter =
             mock(PackageManagerAdapter.class);
     private final BroadcastDispatcher mMockBroadcastDispatcher =
@@ -98,6 +114,11 @@
     private TestContextWrapper mWrappedContext;
     private MockitoSession mMockitoSession;
 
+    public TileLifecycleManagerTest(FlagsParameterization flags) {
+        super();
+        mSetFlagsRule.setFlagsParameterization(flags);
+    }
+
     @Before
     public void setUp() throws Exception {
         setPackageEnabled(true);
@@ -263,7 +284,8 @@
     }
 
     @Test
-    public void testNoClickOfNotListeningAnymore() throws Exception {
+    @DisableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+    public void testNoClickIfNotListeningAnymore() throws Exception {
         mStateManager.onTileAdded();
         mStateManager.onStartListening();
         mStateManager.onClick(null);
@@ -279,6 +301,42 @@
     }
 
     @Test
+    @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+    public void testNoClickIfNotListeningBeforeClick() throws Exception {
+        mStateManager.onTileAdded();
+        mStateManager.onStartListening();
+        mStateManager.onStopListening();
+        mStateManager.onClick(null);
+        mStateManager.executeSetBindService(true);
+        mExecutor.runAllReady();
+
+        verifyBind(1);
+        mStateManager.executeSetBindService(false);
+        mExecutor.runAllReady();
+        assertFalse(mContext.isBound(mTileServiceComponentName));
+        verify(mMockTileService, never()).onClick(null);
+    }
+
+    @Test
+    @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+    public void testClickIfStopListeningBeforeProcessedClick() throws Exception {
+        mStateManager.onTileAdded();
+        mStateManager.onStartListening();
+        mStateManager.onClick(null);
+        mStateManager.onStopListening();
+        mStateManager.executeSetBindService(true);
+        mExecutor.runAllReady();
+
+        verifyBind(1);
+        mStateManager.executeSetBindService(false);
+        mExecutor.runAllReady();
+        assertFalse(mContext.isBound(mTileServiceComponentName));
+        InOrder inOrder = Mockito.inOrder(mMockTileService);
+        inOrder.verify(mMockTileService).onClick(null);
+        inOrder.verify(mMockTileService).onStopListening();
+    }
+
+    @Test
     public void testComponentEnabling() throws Exception {
         mStateManager.onTileAdded();
         mStateManager.onStartListening();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
index 0ff29db..1c86638 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
@@ -15,12 +15,18 @@
  */
 package com.android.systemui.qs.external;
 
+import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf;
+
+import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX;
+import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -32,16 +38,19 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.After;
 import org.junit.Before;
@@ -51,10 +60,20 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
+
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
 public class TileServiceManagerTest extends SysuiTestCase {
 
+    @Parameters(name = "{0}")
+    public static List<FlagsParameterization> getParams() {
+        return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX);
+    }
+
     @Mock
     private TileServices mTileServices;
     @Mock
@@ -68,17 +87,22 @@
     @Mock
     private CustomTileAddedRepository mCustomTileAddedRepository;
 
-    private HandlerThread mThread;
-    private Handler mHandler;
+    private FakeExecutor mFakeExecutor;
+
     private TileServiceManager mTileServiceManager;
     private ComponentName mComponentName;
 
+    public TileServiceManagerTest(FlagsParameterization flags) {
+        super();
+        mSetFlagsRule.setFlagsParameterization(flags);
+    }
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mThread = new HandlerThread("TestThread");
-        mThread.start();
-        mHandler = Handler.createAsync(mThread.getLooper());
+        mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+        Handler handler = mockExecutorHandler(mFakeExecutor);
+
         when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
         when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM);
 
@@ -90,13 +114,12 @@
         mComponentName = new ComponentName(mContext, TileServiceManagerTest.class);
         when(mTileLifecycle.getComponent()).thenReturn(mComponentName);
 
-        mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mUserTracker,
+        mTileServiceManager = new TileServiceManager(mTileServices, handler, mUserTracker,
                 mCustomTileAddedRepository, mTileLifecycle);
     }
 
     @After
     public void tearDown() throws Exception {
-        mThread.quit();
         mTileServiceManager.handleDestroy();
     }
 
@@ -201,4 +224,59 @@
         verify(mTileLifecycle, times(2)).executeSetBindService(captor.capture());
         assertFalse((boolean) captor.getValue());
     }
+
+    @Test
+    @DisableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+    public void testStopListeningAndUnbindImmediatelyAfterUpdate() {
+        when(mTileLifecycle.isActiveTile()).thenReturn(true);
+        mTileServiceManager.startLifecycleManagerAndAddTile();
+        mTileServiceManager.setBindAllowed(true);
+        clearInvocations(mTileLifecycle);
+
+        mTileServiceManager.setBindRequested(true);
+        verify(mTileLifecycle).executeSetBindService(true);
+
+        mTileServiceManager.setLastUpdate(0);
+        mFakeExecutor.advanceClockToLast();
+        mFakeExecutor.runAllReady();
+        verify(mTileLifecycle).onStopListening();
+        verify(mTileLifecycle).executeSetBindService(false);
+    }
+
+    @Test
+    @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+    public void testStopListeningAndUnbindImmediatelyAfterUpdate_ifRequestedFromTileService() {
+        when(mTileLifecycle.isActiveTile()).thenReturn(true);
+        mTileServiceManager.startLifecycleManagerAndAddTile();
+        mTileServiceManager.setBindAllowed(true);
+        clearInvocations(mTileLifecycle);
+
+        mTileServiceManager.setBindRequested(true);
+        mTileServiceManager.onStartListeningFromRequest();
+        verify(mTileLifecycle).onStartListening();
+
+        mTileServiceManager.setLastUpdate(0);
+        mFakeExecutor.advanceClockToLast();
+        mFakeExecutor.runAllReady();
+        verify(mTileLifecycle).onStopListening();
+        verify(mTileLifecycle).executeSetBindService(false);
+    }
+
+    @Test
+    @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+    public void testNotUnbindImmediatelyAfterUpdate_ifRequestedFromSystemUI() {
+        when(mTileLifecycle.isActiveTile()).thenReturn(true);
+        mTileServiceManager.startLifecycleManagerAndAddTile();
+        mTileServiceManager.setBindAllowed(true);
+        clearInvocations(mTileLifecycle);
+
+        mTileServiceManager.setBindRequested(true);
+        // The tile requests startListening (because a click happened)
+
+        mTileServiceManager.setLastUpdate(0);
+        mFakeExecutor.advanceClockToLast();
+        mFakeExecutor.runAllReady();
+        verify(mTileLifecycle, never()).onStopListening();
+        verify(mTileLifecycle, never()).executeSetBindService(false);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index b62d59d..bcff88a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -15,6 +15,10 @@
  */
 package com.android.systemui.qs.external;
 
+import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf;
+
+import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 
@@ -33,8 +37,10 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
 import android.service.quicksettings.IQSTileService;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
@@ -64,13 +70,23 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.List;
 
 import javax.inject.Provider;
 
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(ParameterizedAndroidJunit4.class)
 @RunWithLooper
 public class TileServicesTest extends SysuiTestCase {
+
+    @Parameters(name = "{0}")
+    public static List<FlagsParameterization> getParams() {
+        return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX);
+    }
+
     private static int NUM_FAKES = TileServices.DEFAULT_MAX_BOUND * 2;
 
     private static final ComponentName TEST_COMPONENT =
@@ -106,6 +122,11 @@
     @Mock
     private CustomTileAddedRepository mCustomTileAddedRepository;
 
+    public TileServicesTest(FlagsParameterization flags) {
+        super();
+        mSetFlagsRule.setFlagsParameterization(flags);
+    }
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -194,6 +215,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
     public void testRequestListeningStatusCommand() throws RemoteException {
         ArgumentCaptor<CommandQueue.Callbacks> captor =
                 ArgumentCaptor.forClass(CommandQueue.Callbacks.class);
@@ -213,6 +235,26 @@
     }
 
     @Test
+    @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+    public void testRequestListeningStatusCommand_onStartListeningFromRequest() {
+        ArgumentCaptor<CommandQueue.Callbacks> captor =
+                ArgumentCaptor.forClass(CommandQueue.Callbacks.class);
+        verify(mCommandQueue).addCallback(captor.capture());
+
+        CustomTile mockTile = mock(CustomTile.class);
+        when(mockTile.getComponent()).thenReturn(TEST_COMPONENT);
+
+        TileServiceManager manager = mTileService.getTileWrapper(mockTile);
+        when(manager.isActiveTile()).thenReturn(true);
+        when(manager.getTileService()).thenReturn(mock(IQSTileService.class));
+
+        captor.getValue().requestTileServiceListeningState(TEST_COMPONENT);
+        mTestableLooper.processAllMessages();
+        verify(manager).setBindRequested(true);
+        verify(manager).onStartListeningFromRequest();
+    }
+
+    @Test
     public void testValidCustomTileStartsActivity() {
         CustomTile tile = mock(CustomTile.class);
         PendingIntent pi = mock(PendingIntent.class);
@@ -263,6 +305,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
     public void tileFreedForCorrectUser() throws RemoteException {
         verify(mCommandQueue).addCallback(mCallbacksArgumentCaptor.capture());
 
@@ -297,6 +340,42 @@
         verify(manager1.getTileService()).onStartListening();
     }
 
+    @Test
+    @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+    public void tileFreedForCorrectUser_onStartListeningFromRequest() throws RemoteException {
+        verify(mCommandQueue).addCallback(mCallbacksArgumentCaptor.capture());
+
+        ComponentName componentName = new ComponentName("pkg", "cls");
+        CustomTile tileUser0 = mock(CustomTile.class);
+        CustomTile tileUser1 = mock(CustomTile.class);
+
+        when(tileUser0.getComponent()).thenReturn(componentName);
+        when(tileUser1.getComponent()).thenReturn(componentName);
+        when(tileUser0.getUser()).thenReturn(0);
+        when(tileUser1.getUser()).thenReturn(1);
+
+        // Create a tile for user 0
+        TileServiceManager manager0 = mTileService.getTileWrapper(tileUser0);
+        when(manager0.isActiveTile()).thenReturn(true);
+        // Then create a tile for user 1
+        TileServiceManager manager1 = mTileService.getTileWrapper(tileUser1);
+        when(manager1.isActiveTile()).thenReturn(true);
+
+        // When the tile for user 0 gets freed
+        mTileService.freeService(tileUser0, manager0);
+        // and the user is 1
+        when(mUserTracker.getUserId()).thenReturn(1);
+
+        // a call to requestListeningState
+        mCallbacksArgumentCaptor.getValue().requestTileServiceListeningState(componentName);
+        mTestableLooper.processAllMessages();
+
+        // will call in the correct tile
+        verify(manager1).setBindRequested(true);
+        // and set it to listening
+        verify(manager1).onStartListeningFromRequest();
+    }
+
     private class TestTileServices extends TileServices {
         TestTileServices(QSHost host, Provider<Handler> handlerProvider,
                 BroadcastDispatcher broadcastDispatcher, UserTracker userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
new file mode 100644
index 0000000..d15cfbf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
@@ -0,0 +1,191 @@
+/*
+ * 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.qs.panels.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.panels.data.repository.IconTilesRepository
+import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository
+import com.android.systemui.qs.panels.data.repository.iconTilesRepository
+import com.android.systemui.qs.panels.shared.model.GridLayoutType
+import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
+import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class GridConsistencyInteractorTest : SysuiTestCase() {
+
+    data object TestGridLayoutType : GridLayoutType
+
+    private val iconOnlyTiles =
+        MutableStateFlow(
+            setOf(
+                TileSpec.create("smallA"),
+                TileSpec.create("smallB"),
+                TileSpec.create("smallC"),
+                TileSpec.create("smallD"),
+                TileSpec.create("smallE"),
+            )
+        )
+
+    private val kosmos =
+        testKosmos().apply {
+            iconTilesRepository =
+                object : IconTilesRepository {
+                    override val iconTilesSpecs: StateFlow<Set<TileSpec>>
+                        get() = iconOnlyTiles.asStateFlow()
+                }
+            gridConsistencyInteractorsMap =
+                mapOf(
+                    Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor),
+                    Pair(TestGridLayoutType, noopGridConsistencyInteractor)
+                )
+        }
+
+    private val underTest = with(kosmos) { gridConsistencyInteractor }
+
+    @Before
+    fun setUp() {
+        with(kosmos) { gridLayoutTypeRepository.setLayout(InfiniteGridLayoutType) }
+        underTest.start()
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun changeLayoutType_usesCorrectGridConsistencyInteractor() =
+        with(kosmos) {
+            testScope.runTest {
+                // Using the no-op grid consistency interactor
+                gridLayoutTypeRepository.setLayout(TestGridLayoutType)
+
+                // Setting an invalid layout with holes
+                // [ Large A ] [ sa ]
+                // [ Large B ] [ Large C ]
+                // [ sb ] [ Large D ]
+                val newTiles =
+                    listOf(
+                        TileSpec.create("largeA"),
+                        TileSpec.create("smallA"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("largeC"),
+                        TileSpec.create("smallB"),
+                        TileSpec.create("largeD"),
+                    )
+                tileSpecRepository.setTiles(0, newTiles)
+
+                runCurrent()
+
+                val tiles = currentTilesInteractor.currentTiles.value
+                val tileSpecs = tiles.map { it.spec }
+
+                // Saved tiles should be unchanged
+                assertThat(tileSpecs).isEqualTo(newTiles)
+            }
+        }
+
+    @Test
+    fun validTilesWithInfiniteGridConsistencyInteractor_unchangedList() =
+        with(kosmos) {
+            testScope.runTest {
+                // Setting a valid layout with holes
+                // [ Large A ] [ sa ][ sb ]
+                // [ Large B ] [ Large C ]
+                // [ Large D ]
+                val newTiles =
+                    listOf(
+                        TileSpec.create("largeA"),
+                        TileSpec.create("smallA"),
+                        TileSpec.create("smallB"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("largeC"),
+                        TileSpec.create("largeD"),
+                    )
+                tileSpecRepository.setTiles(0, newTiles)
+
+                runCurrent()
+
+                val tiles = currentTilesInteractor.currentTiles.value
+                val tileSpecs = tiles.map { it.spec }
+
+                // Saved tiles should be unchanged
+                assertThat(tileSpecs).isEqualTo(newTiles)
+            }
+        }
+
+    @Test
+    fun invalidTilesWithInfiniteGridConsistencyInteractor_savesNewList() =
+        with(kosmos) {
+            testScope.runTest {
+                // Setting an invalid layout with holes
+                // [ sa ] [ Large A ]
+                // [ Large B ] [ sb ] [ sc ]
+                // [ sd ] [ se ] [ Large C ]
+                val newTiles =
+                    listOf(
+                        TileSpec.create("smallA"),
+                        TileSpec.create("largeA"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("smallB"),
+                        TileSpec.create("smallC"),
+                        TileSpec.create("smallD"),
+                        TileSpec.create("smallE"),
+                        TileSpec.create("largeC"),
+                    )
+                tileSpecRepository.setTiles(0, newTiles)
+
+                runCurrent()
+
+                val tiles = currentTilesInteractor.currentTiles.value
+                val tileSpecs = tiles.map { it.spec }
+
+                // Expected grid
+                // [ sa ] [ Large A ] [ sb ]
+                // [ Large B ] [ sc ] [ sd ]
+                // [ se ] [ Large C ]
+                val expectedTiles =
+                    listOf(
+                        TileSpec.create("smallA"),
+                        TileSpec.create("largeA"),
+                        TileSpec.create("smallB"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("smallC"),
+                        TileSpec.create("smallD"),
+                        TileSpec.create("smallE"),
+                        TileSpec.create("largeC"),
+                    )
+
+                // Saved tiles should be unchanged
+                assertThat(tileSpecs).isEqualTo(expectedTiles)
+            }
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
new file mode 100644
index 0000000..bda48ad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
@@ -0,0 +1,195 @@
+/*
+ * 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.qs.panels.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.panels.data.repository.IconTilesRepository
+import com.android.systemui.qs.panels.data.repository.iconTilesRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class InfiniteGridConsistencyInteractorTest : SysuiTestCase() {
+
+    private val iconOnlyTiles =
+        MutableStateFlow(
+            setOf(
+                TileSpec.create("smallA"),
+                TileSpec.create("smallB"),
+                TileSpec.create("smallC"),
+                TileSpec.create("smallD"),
+                TileSpec.create("smallE"),
+            )
+        )
+    private val kosmos =
+        testKosmos().apply {
+            iconTilesRepository =
+                object : IconTilesRepository {
+                    override val iconTilesSpecs: StateFlow<Set<TileSpec>>
+                        get() = iconOnlyTiles.asStateFlow()
+                }
+        }
+    private val underTest = with(kosmos) { infiniteGridConsistencyInteractor }
+
+    @Test
+    fun validTiles_returnsUnchangedList() =
+        with(kosmos) {
+            testScope.runTest {
+                // Original grid
+                // [ Large A ] [ sa ][ sb ]
+                // [ Large B ] [ Large C ]
+                // [ Large D ]
+                val tiles =
+                    listOf(
+                        TileSpec.create("largeA"),
+                        TileSpec.create("smallA"),
+                        TileSpec.create("smallB"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("largeC"),
+                        TileSpec.create("largeD"),
+                    )
+
+                val newTiles = underTest.reconcileTiles(tiles)
+
+                assertThat(newTiles).isEqualTo(tiles)
+            }
+        }
+
+    @Test
+    fun invalidTiles_moveIconTileForward() =
+        with(kosmos) {
+            testScope.runTest {
+                // Original grid
+                // [ Large A ] [ sa ]
+                // [ Large B ] [ Large C ]
+                // [ sb ] [ Large D ]
+                val tiles =
+                    listOf(
+                        TileSpec.create("largeA"),
+                        TileSpec.create("smallA"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("largeC"),
+                        TileSpec.create("smallB"),
+                        TileSpec.create("largeD"),
+                    )
+                // Expected grid
+                // [ Large A ] [ sa ][ sb ]
+                // [ Large B ] [ Large C ]
+                // [ Large D ]
+                val expectedTiles =
+                    listOf(
+                        TileSpec.create("largeA"),
+                        TileSpec.create("smallA"),
+                        TileSpec.create("smallB"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("largeC"),
+                        TileSpec.create("largeD"),
+                    )
+
+                val newTiles = underTest.reconcileTiles(tiles)
+
+                assertThat(newTiles).isEqualTo(expectedTiles)
+            }
+        }
+
+    @Test
+    fun invalidTiles_moveIconTileBack() =
+        with(kosmos) {
+            testScope.runTest {
+                // Original grid
+                // [ sa ] [ Large A ]
+                // [ Large B ] [ Large C ]
+                // [ Large D ]
+                val tiles =
+                    listOf(
+                        TileSpec.create("smallA"),
+                        TileSpec.create("largeA"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("largeC"),
+                        TileSpec.create("largeD"),
+                    )
+                // Expected grid
+                // [ Large A ] [ Large B ]
+                // [ Large C ] [ Large D ]
+                // [ sa ]
+                val expectedTiles =
+                    listOf(
+                        TileSpec.create("largeA"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("largeC"),
+                        TileSpec.create("largeD"),
+                        TileSpec.create("smallA"),
+                    )
+
+                val newTiles = underTest.reconcileTiles(tiles)
+
+                assertThat(newTiles).isEqualTo(expectedTiles)
+            }
+        }
+
+    @Test
+    fun invalidTiles_multipleCorrections() =
+        with(kosmos) {
+            testScope.runTest {
+                // Original grid
+                // [ sa ] [ Large A ]
+                // [ Large B ] [ sb ] [ sc ]
+                // [ sd ] [ se ] [ Large C ]
+                val tiles =
+                    listOf(
+                        TileSpec.create("smallA"),
+                        TileSpec.create("largeA"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("smallB"),
+                        TileSpec.create("smallC"),
+                        TileSpec.create("smallD"),
+                        TileSpec.create("smallE"),
+                        TileSpec.create("largeC"),
+                    )
+                // Expected grid
+                // [ sa ] [ Large A ] [ sb ]
+                // [ Large B ] [ sc ] [ sd ]
+                // [ se ] [ Large C ]
+                val expectedTiles =
+                    listOf(
+                        TileSpec.create("smallA"),
+                        TileSpec.create("largeA"),
+                        TileSpec.create("smallB"),
+                        TileSpec.create("largeB"),
+                        TileSpec.create("smallC"),
+                        TileSpec.create("smallD"),
+                        TileSpec.create("smallE"),
+                        TileSpec.create("largeC"),
+                    )
+
+                val newTiles = underTest.reconcileTiles(tiles)
+
+                assertThat(newTiles).isEqualTo(expectedTiles)
+            }
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/repository/IconTilesRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/repository/IconTilesRepositoryImplTest.kt
deleted file mode 100644
index 8cc3a85..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/repository/IconTilesRepositoryImplTest.kt
+++ /dev/null
@@ -1,61 +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.
- */
-
-package com.android.systemui.qs.panels.domain.repository
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.qs.panels.data.repository.IconTilesRepositoryImpl
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class IconTilesRepositoryImplTest : SysuiTestCase() {
-
-    private val underTest = IconTilesRepositoryImpl()
-
-    @Test
-    fun iconTilesSpecsIsValid() = runTest {
-        val tilesSpecs by collectLastValue(underTest.iconTilesSpecs)
-        assertThat(tilesSpecs).isEqualTo(ICON_ONLY_TILES_SPECS)
-    }
-
-    companion object {
-        private val ICON_ONLY_TILES_SPECS =
-            setOf(
-                TileSpec.create("airplane"),
-                TileSpec.create("battery"),
-                TileSpec.create("cameratoggle"),
-                TileSpec.create("cast"),
-                TileSpec.create("color_correction"),
-                TileSpec.create("inversion"),
-                TileSpec.create("saver"),
-                TileSpec.create("dnd"),
-                TileSpec.create("flashlight"),
-                TileSpec.create("location"),
-                TileSpec.create("mictoggle"),
-                TileSpec.create("nfc"),
-                TileSpec.create("night"),
-                TileSpec.create("rotation")
-            )
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index b5ef8c2..db11c3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -500,6 +500,42 @@
         )
     }
 
+    @Test
+    fun onActivityLaunchAnimationEnd_onFreshTile_longPressPropertiesAreReset() {
+        // WHEN an activity launch animation ends on a fresh tile
+        tileView.onActivityLaunchAnimationEnd()
+
+        // THEN the tile's long-press effect properties are reset by default
+        assertThat(tileView.haveLongPressPropertiesBeenReset).isTrue()
+    }
+
+    @Test
+    fun onUpdateLongPressEffectProperties_duringLongPressEffect_propertiesAreNotReset() {
+        // GIVEN a state that supports long-press
+        val state = QSTile.State()
+        tileView.changeState(state)
+
+        // WHEN the long-press effect is updating the properties
+        tileView.updateLongPressEffectProperties(1f)
+
+        // THEN the tile's long-press effect properties haven't reset
+        assertThat(tileView.haveLongPressPropertiesBeenReset).isFalse()
+    }
+
+    @Test
+    fun onActivityLaunchAnimationEnd_afterLongPressEffect_longPressPropertiesAreReset() {
+        // GIVEN a state that supports long-press and the long-press effect updating
+        val state = QSTile.State()
+        tileView.changeState(state)
+        tileView.updateLongPressEffectProperties(1f)
+
+        // WHEN an activity launch animation ends on a fresh tile
+        tileView.onActivityLaunchAnimationEnd()
+
+        // THEN the tile's long-press effect properties are reset
+        assertThat(tileView.haveLongPressPropertiesBeenReset).isTrue()
+    }
+
     class FakeTileView(
         context: Context,
         collapsed: Boolean,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
index e2a3fac6..ad87315 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
@@ -22,7 +22,7 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
+import com.android.internal.telephony.flags.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.classifier.FalsingManagerFake
@@ -33,10 +33,12 @@
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.settings.GlobalSettings
 import com.google.common.truth.Truth.assertThat
 import dagger.Lazy
+import kotlinx.coroutines.Job
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -44,11 +46,15 @@
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
 
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 class AirplaneModeTileTest : SysuiTestCase() {
+
     @Mock
     private lateinit var mHost: QSHost
     @Mock
@@ -62,7 +68,9 @@
     @Mock
     private lateinit var mBroadcastDispatcher: BroadcastDispatcher
     @Mock
-    private lateinit var mConnectivityManager: Lazy<ConnectivityManager>
+    private lateinit var mLazyConnectivityManager: Lazy<ConnectivityManager>
+    @Mock
+    private lateinit var mConnectivityManager: ConnectivityManager
     @Mock
     private lateinit var mGlobalSettings: GlobalSettings
     @Mock
@@ -72,13 +80,15 @@
     private lateinit var mTestableLooper: TestableLooper
     private lateinit var mTile: AirplaneModeTile
 
+    @Mock
+    private lateinit var mClickJob: Job
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         mTestableLooper = TestableLooper.get(this)
         Mockito.`when`(mHost.context).thenReturn(mContext)
         Mockito.`when`(mHost.userContext).thenReturn(mContext)
-
+        Mockito.`when`(mLazyConnectivityManager.get()).thenReturn(mConnectivityManager)
         mTile = AirplaneModeTile(
             mHost,
             mUiEventLogger,
@@ -90,7 +100,7 @@
             mActivityStarter,
             mQsLogger,
             mBroadcastDispatcher,
-            mConnectivityManager,
+            mLazyConnectivityManager,
             mGlobalSettings,
             mUserTracker)
     }
@@ -120,4 +130,24 @@
         assertThat(state.icon)
             .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_airplane_icon_on))
     }
+
+    @Test
+    fun handleClick_noSatelliteFeature_directSetAirplaneMode() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+
+        mTile.handleClick(null)
+
+        verify(mConnectivityManager).setAirplaneMode(any())
+    }
+
+    @Test
+    fun handleClick_hasSatelliteFeatureButClickIsProcessing_doNothing() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        Mockito.`when`(mClickJob.isCompleted).thenReturn(false)
+        mTile.mClickJob = mClickJob
+
+        mTile.handleClick(null)
+
+        verify(mConnectivityManager, times(0)).setAirplaneMode(any())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index 830f08a..1ffbb7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -9,10 +9,11 @@
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
+import com.android.internal.telephony.flags.Flags
 import com.android.settingslib.Utils
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.plugins.ActivityStarter
@@ -23,13 +24,14 @@
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
-import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.BluetoothController
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Job
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -37,6 +39,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -54,7 +57,7 @@
     @Mock private lateinit var uiEventLogger: QsEventLogger
     @Mock private lateinit var featureFlags: FeatureFlagsClassic
     @Mock private lateinit var bluetoothTileDialogViewModel: BluetoothTileDialogViewModel
-
+    @Mock private lateinit var clickJob: Job
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: FakeBluetoothTile
 
@@ -191,6 +194,41 @@
     }
 
     @Test
+    fun handleClick_hasSatelliteFeatureButNoQsTileDialogAndClickIsProcessing_doNothing() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
+                .thenReturn(false)
+        `when`(clickJob.isCompleted).thenReturn(false)
+        tile.mClickJob = clickJob
+
+        tile.handleClick(null)
+
+        verify(bluetoothController, times(0)).setBluetoothEnabled(any())
+    }
+
+    @Test
+    fun handleClick_noSatelliteFeatureAndNoQsTileDialog_directSetBtEnable() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
+                .thenReturn(false)
+
+        tile.handleClick(null)
+
+        verify(bluetoothController).setBluetoothEnabled(any())
+    }
+
+    @Test
+    fun handleClick_noSatelliteFeatureButHasQsTileDialog_showDialog() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
+                .thenReturn(true)
+
+        tile.handleClick(null)
+
+        verify(bluetoothTileDialogViewModel).showDialog(null)
+    }
+
+    @Test
     fun testMetadataListener_whenDisconnected_isUnregistered() {
         val state = QSTile.BooleanState()
         val cachedDevice = mock<CachedBluetoothDevice>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
index 2536a93..9798562 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
@@ -5,6 +5,7 @@
 import static android.telephony.SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
 import static android.telephony.SignalStrength.SIGNAL_STRENGTH_GREAT;
 import static android.telephony.SignalStrength.SIGNAL_STRENGTH_POOR;
+import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.settingslib.wifi.WifiUtils.getHotspotIconResource;
@@ -217,6 +218,8 @@
         when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry);
         when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
         when(SubscriptionManager.getDefaultDataSubscriptionId()).thenReturn(SUB_ID);
+        SubscriptionInfo info = mock(SubscriptionInfo.class);
+        when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(info);
         when(mToastFactory.createToast(any(), anyString(), anyString(), anyInt(), anyInt()))
             .thenReturn(mSystemUIToast);
         when(mSystemUIToast.getView()).thenReturn(mToastView);
@@ -1083,19 +1086,34 @@
     }
 
     @Test
-    public void hasActiveSubId_activeSubIdListIsEmpty_returnFalse() {
-        when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{});
+    public void hasActiveSubIdOnDds_noDds_returnFalse() {
+        when(SubscriptionManager.getDefaultDataSubscriptionId())
+                .thenReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
         mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
 
-        assertThat(mInternetDialogController.hasActiveSubId()).isFalse();
+        assertThat(mInternetDialogController.hasActiveSubIdOnDds()).isFalse();
     }
 
     @Test
-    public void hasActiveSubId_activeSubIdListNotEmpty_returnTrue() {
-        when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
+    public void hasActiveSubIdOnDds_activeDds_returnTrue() {
         mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
 
-        assertThat(mInternetDialogController.hasActiveSubId()).isTrue();
+        assertThat(mInternetDialogController.hasActiveSubIdOnDds()).isTrue();
+    }
+
+    @Test
+    public void hasActiveSubIdOnDds_activeDdsAndHasProvisioning_returnFalse() {
+        when(SubscriptionManager.getDefaultDataSubscriptionId())
+                .thenReturn(SUB_ID);
+        SubscriptionInfo info = mock(SubscriptionInfo.class);
+        when(info.isEmbedded()).thenReturn(true);
+        when(info.getProfileClass()).thenReturn(PROFILE_CLASS_PROVISIONING);
+        when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(info);
+
+        mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
+
+        assertThat(mInternetDialogController.hasActiveSubIdOnDds()).isFalse();
     }
 
     private String getResourcesString(String name) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
index 6f88891..aefcc87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
@@ -251,7 +251,7 @@
         // Mobile network should be gone if the list of active subscriptionId is null.
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(false);
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
-        when(mInternetDialogController.hasActiveSubId()).thenReturn(false);
+        when(mInternetDialogController.hasActiveSubIdOnDds()).thenReturn(false);
 
         mInternetDialogDelegate.updateDialog(true);
 
@@ -336,7 +336,7 @@
 
     @Test
     public void updateDialog_mobileDataIsEnabled_checkMobileDataSwitch() {
-        doReturn(true).when(mInternetDialogController).hasActiveSubId();
+        doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds();
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
         when(mInternetDialogController.isMobileDataEnabled()).thenReturn(true);
         mMobileToggleSwitch.setChecked(false);
@@ -348,7 +348,7 @@
 
     @Test
     public void updateDialog_mobileDataIsNotChanged_checkMobileDataSwitch() {
-        doReturn(true).when(mInternetDialogController).hasActiveSubId();
+        doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds();
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
         when(mInternetDialogController.isMobileDataEnabled()).thenReturn(false);
         mMobileToggleSwitch.setChecked(false);
@@ -361,7 +361,7 @@
     @Test
     public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() {
         mInternetDialogDelegate.dismissDialog();
-        doReturn(true).when(mInternetDialogController).hasActiveSubId();
+        doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds();
         createInternetDialog();
         // The preconditions WiFi ON and Internet WiFi are already in setUp()
         doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
@@ -522,7 +522,7 @@
     public void updateDialog_showSecondaryDataSub() {
         mInternetDialogDelegate.dismissDialog();
         doReturn(1).when(mInternetDialogController).getActiveAutoSwitchNonDdsSubId();
-        doReturn(true).when(mInternetDialogController).hasActiveSubId();
+        doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds();
         doReturn(false).when(mInternetDialogController).isAirplaneModeEnabled();
         createInternetDialog();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
index f88a5a0..b75b318 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
@@ -20,7 +20,7 @@
 import static junit.framework.Assert.assertNotSame;
 
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -81,7 +81,7 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
-        when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
+        when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
         when(mSystemUIDialogFactory.create()).thenReturn(mSystemUIDialog);
         when(mSystemUIDialog.getContext()).thenReturn(mContext);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index effae5f..74deae3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -72,7 +72,7 @@
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.atLeast
 import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.intThat
+import org.mockito.Mockito.longThat
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.times
@@ -162,7 +162,7 @@
 
         verify(overviewProxy)
             .onSystemUiStateChanged(
-                intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_AWAKE }
+                longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_AWAKE }
             )
     }
 
@@ -172,7 +172,7 @@
 
         verify(overviewProxy)
             .onSystemUiStateChanged(
-                intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_WAKING }
+                longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_WAKING }
             )
     }
 
@@ -182,7 +182,7 @@
 
         verify(overviewProxy)
             .onSystemUiStateChanged(
-                intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_ASLEEP }
+                longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_ASLEEP }
             )
     }
 
@@ -194,7 +194,7 @@
 
         verify(overviewProxy)
             .onSystemUiStateChanged(
-                intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP }
+                longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP }
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index 6846c72..fcc6b4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -55,6 +55,7 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
@@ -94,7 +95,7 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
         whenever(dprLazy.get()).thenReturn(devicePolicyResolver)
-        whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
+        whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState)
         whenever(screenCaptureDisabledDialogDelegate.createSysUIDialog())
             .thenReturn(screenCaptureDisabledDialog)
         whenever(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
index 5e7d8fb..a10d81f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
@@ -21,7 +21,6 @@
 import android.os.Bundle
 import android.os.UserHandle
 import android.testing.AndroidTestingRunner
-import android.view.View
 import android.view.Window
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -49,7 +48,7 @@
 
     private val intentExecutor = mock<ActionIntentExecutor>()
     private val window = mock<Window>()
-    private val view = mock<View>()
+    private val viewProxy = mock<ScreenshotShelfViewProxy>()
     private val onDismiss = mock<(() -> Unit)>()
     private val pendingIntent = mock<PendingIntent>()
 
@@ -70,16 +69,16 @@
     }
 
     @Test
-    fun sendPendingIntent_dismisses() = runTest {
+    fun sendPendingIntent_requestsDismissal() = runTest {
         actionExecutor = createActionExecutor()
 
         actionExecutor.sendPendingIntent(pendingIntent)
 
         verify(pendingIntent).send(any(Bundle::class.java))
-        verify(onDismiss).invoke()
+        verify(viewProxy).requestDismissal(null)
     }
 
     private fun createActionExecutor(): ActionExecutor {
-        return ActionExecutor(intentExecutor, testScope, window, view, onDismiss)
+        return ActionExecutor(intentExecutor, testScope, window, viewProxy, onDismiss)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
index 5e53fe1..5cd3f66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
@@ -23,17 +23,18 @@
 import android.testing.TestableContext
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.screenshot.proxy.SystemUiProxy
 import com.android.systemui.settings.DisplayTracker
 import com.android.systemui.shared.system.ActivityManagerWrapper
 import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.util.mockito.mock
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestCoroutineScheduler
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito.verify
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
 
 @RunWith(AndroidTestingRunner::class)
 class ActionIntentExecutorTest : SysuiTestCase() {
@@ -44,8 +45,9 @@
     private val testableContext = TestableContext(mContext)
 
     private val activityManagerWrapper = mock<ActivityManagerWrapper>()
+    private val systemUiProxy = mock<SystemUiProxy>()
+
     private val displayTracker = mock<DisplayTracker>()
-    private val keyguardController = mock<ScreenshotKeyguardController>()
 
     private val actionIntentExecutor =
         ActionIntentExecutor(
@@ -53,12 +55,12 @@
             activityManagerWrapper,
             testScope,
             mainDispatcher,
+            systemUiProxy,
             displayTracker,
-            keyguardController,
         )
 
     @Test
-    @EnableFlags(Flags.FLAG_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS)
+    @EnableFlags(Flags.FLAG_FIX_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS)
     fun launchIntent_callsCloseSystemWindows() =
         testScope.runTest {
             val intent = Intent(Intent.ACTION_EDIT).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
index 853e50a..896c3bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
@@ -37,6 +37,7 @@
 import org.junit.Before
 import org.junit.runner.RunWith
 import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.kotlin.never
 import org.mockito.kotlin.verify
 
 @RunWith(AndroidTestingRunner::class)
@@ -111,6 +112,47 @@
         assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_CHOOSER)
     }
 
+    @Test
+    fun scrollChipClicked_callsOnClick() = runTest {
+        actionsProvider = createActionsProvider()
+
+        val onScrollClick = mock<Runnable>()
+        val numActions = viewModel.actions.value.size
+        actionsProvider.onScrollChipReady(onScrollClick)
+        viewModel.actions.value[numActions].onClicked!!.invoke()
+
+        verify(onScrollClick).run()
+    }
+
+    @Test
+    fun scrollChipClicked_afterInvalidate_doesNothing() = runTest {
+        actionsProvider = createActionsProvider()
+
+        val onScrollClick = mock<Runnable>()
+        val numActions = viewModel.actions.value.size
+        actionsProvider.onScrollChipReady(onScrollClick)
+        actionsProvider.onScrollChipInvalidated()
+        viewModel.actions.value[numActions].onClicked!!.invoke()
+
+        verify(onScrollClick, never()).run()
+    }
+
+    @Test
+    fun scrollChipClicked_afterUpdate_runsNewAction() = runTest {
+        actionsProvider = createActionsProvider()
+
+        val onScrollClick = mock<Runnable>()
+        val onScrollClick2 = mock<Runnable>()
+        val numActions = viewModel.actions.value.size
+        actionsProvider.onScrollChipReady(onScrollClick)
+        actionsProvider.onScrollChipInvalidated()
+        actionsProvider.onScrollChipReady(onScrollClick2)
+        viewModel.actions.value[numActions].onClicked!!.invoke()
+
+        verify(onScrollClick2).run()
+        verify(onScrollClick, never()).run()
+    }
+
     private fun createActionsProvider(): ScreenshotActionsProvider {
         return DefaultScreenshotActionsProvider(
             context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index 0f37143..bf7d909 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -69,8 +69,9 @@
 
     @Before
     fun setUp() {
-        whenever(controllerFactory.create(eq(0), any())).thenReturn(controller0)
-        whenever(controllerFactory.create(eq(1), any())).thenReturn(controller1)
+        whenever(controllerFactory.create(any(), any())).thenAnswer {
+            if (it.getArgument<Display>(0).displayId == 0) controller0 else controller1
+        }
         whenever(notificationControllerFactory.create(eq(0))).thenReturn(notificationsController0)
         whenever(notificationControllerFactory.create(eq(1))).thenReturn(notificationsController1)
     }
@@ -78,12 +79,14 @@
     @Test
     fun executeScreenshots_severalDisplays_callsControllerForEachOne() =
         testScope.runTest {
-            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+            val internalDisplay = display(TYPE_INTERNAL, id = 0)
+            val externalDisplay = display(TYPE_EXTERNAL, id = 1)
+            setDisplays(internalDisplay, externalDisplay)
             val onSaved = { _: Uri? -> }
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
 
-            verify(controllerFactory).create(eq(0), any())
-            verify(controllerFactory).create(eq(1), any())
+            verify(controllerFactory).create(eq(internalDisplay), any())
+            verify(controllerFactory).create(eq(externalDisplay), any())
 
             val capturer = ArgumentCaptor<ScreenshotData>()
 
@@ -107,7 +110,9 @@
     @Test
     fun executeScreenshots_providedImageType_callsOnlyDefaultDisplayController() =
         testScope.runTest {
-            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+            val internalDisplay = display(TYPE_INTERNAL, id = 0)
+            val externalDisplay = display(TYPE_EXTERNAL, id = 1)
+            setDisplays(internalDisplay, externalDisplay)
             val onSaved = { _: Uri? -> }
             screenshotExecutor.executeScreenshots(
                 createScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE),
@@ -115,8 +120,8 @@
                 callback
             )
 
-            verify(controllerFactory).create(eq(0), any())
-            verify(controllerFactory, never()).create(eq(1), any())
+            verify(controllerFactory).create(eq(internalDisplay), any())
+            verify(controllerFactory, never()).create(eq(externalDisplay), any())
 
             val capturer = ArgumentCaptor<ScreenshotData>()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt
index d44e26c..e32086b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt
@@ -34,20 +34,21 @@
 
         assertThat(viewModel.actions.value).isEmpty()
 
-        viewModel.addAction(appearance, onclick)
+        viewModel.addAction(appearance, true, onclick)
 
         assertThat(viewModel.actions.value).hasSize(1)
 
         val added = viewModel.actions.value[0]
         assertThat(added.appearance).isEqualTo(appearance)
         assertThat(added.onClicked).isEqualTo(onclick)
+        assertThat(added.showDuringEntrance).isTrue()
     }
 
     @Test
     fun testRemoveAction() {
         val viewModel = ScreenshotViewModel(accessibilityManager)
-        val firstId = viewModel.addAction(ActionButtonAppearance(null, "", ""), {})
-        val secondId = viewModel.addAction(appearance, onclick)
+        val firstId = viewModel.addAction(ActionButtonAppearance(null, "", ""), false, {})
+        val secondId = viewModel.addAction(appearance, false, onclick)
 
         assertThat(viewModel.actions.value).hasSize(2)
         assertThat(firstId).isNotEqualTo(secondId)
@@ -58,13 +59,14 @@
 
         val remaining = viewModel.actions.value[0]
         assertThat(remaining.appearance).isEqualTo(appearance)
+        assertThat(remaining.showDuringEntrance).isFalse()
         assertThat(remaining.onClicked).isEqualTo(onclick)
     }
 
     @Test
     fun testUpdateActionAppearance() {
         val viewModel = ScreenshotViewModel(accessibilityManager)
-        val id = viewModel.addAction(appearance, onclick)
+        val id = viewModel.addAction(appearance, false, onclick)
         val otherAppearance = ActionButtonAppearance(null, "Other", "Other")
 
         viewModel.updateActionAppearance(id, otherAppearance)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 99204e7..49a467e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -18,7 +18,7 @@
 
 import android.graphics.Rect
 import android.os.PowerManager
-import android.platform.test.flag.junit.FlagsParameterization
+import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.ViewUtils
 import android.view.MotionEvent
@@ -27,6 +27,7 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
@@ -39,26 +40,22 @@
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
 import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.ui.compose.CommunalContent
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.util.CommunalColors
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
 import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
@@ -71,14 +68,12 @@
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4
-import platform.test.runner.parameterized.Parameters
 
 @ExperimentalCoroutinesApi
-@RunWith(ParameterizedAndroidJunit4::class)
+@RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
-class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class GlanceableHubContainerControllerTest : SysuiTestCase() {
     private val kosmos: Kosmos =
         testKosmos().apply {
             // UnconfinedTestDispatcher makes testing simpler due to CommunalInteractor flows using
@@ -88,9 +83,9 @@
 
     @Mock private lateinit var communalViewModel: CommunalViewModel
     @Mock private lateinit var powerManager: PowerManager
-    @Mock private lateinit var dialogFactory: SystemUIDialogFactory
     @Mock private lateinit var touchMonitor: TouchMonitor
     @Mock private lateinit var communalColors: CommunalColors
+    @Mock private lateinit var communalContent: CommunalContent
     private lateinit var ambientTouchComponentFactory: AmbientTouchComponent.Factory
 
     private lateinit var parentView: FrameLayout
@@ -100,10 +95,6 @@
     private lateinit var communalRepository: FakeCommunalRepository
     private lateinit var underTest: GlanceableHubContainerController
 
-    init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
-    }
-
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -126,13 +117,12 @@
                 GlanceableHubContainerController(
                     communalInteractor,
                     communalViewModel,
-                    dialogFactory,
-                    keyguardTransitionInteractor,
                     keyguardInteractor,
                     shadeInteractor,
                     powerManager,
                     communalColors,
                     ambientTouchComponentFactory,
+                    communalContent,
                     kosmos.sceneDataSourceDelegator,
                 )
         }
@@ -169,13 +159,12 @@
                     GlanceableHubContainerController(
                         communalInteractor,
                         communalViewModel,
-                        dialogFactory,
-                        keyguardTransitionInteractor,
                         keyguardInteractor,
                         shadeInteractor,
                         powerManager,
                         communalColors,
                         ambientTouchComponentFactory,
+                        communalContent,
                         kosmos.sceneDataSourceDelegator,
                     )
 
@@ -216,13 +205,39 @@
         }
 
     @Test
+    fun onTouchEvent_communalTransitioning_interceptsTouches() =
+        with(kosmos) {
+            testScope.runTest {
+                // Communal is opening.
+                communalRepository.setTransitionState(
+                    flowOf(
+                        ObservableTransitionState.Transition(
+                            fromScene = CommunalScenes.Blank,
+                            toScene = CommunalScenes.Communal,
+                            currentScene = flowOf(CommunalScenes.Blank),
+                            progress = flowOf(0.5f),
+                            isInitiatedByUserInput = true,
+                            isUserInputOngoing = flowOf(true)
+                        )
+                    )
+                )
+                testableLooper.processAllMessages()
+
+                // Touch events are intercepted.
+                assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+                // User activity sent to PowerManager.
+                verify(powerManager).userActivity(any(), any(), any())
+            }
+        }
+
+    @Test
     fun onTouchEvent_communalOpen_interceptsTouches() =
         with(kosmos) {
             testScope.runTest {
                 // Communal is open.
                 goToScene(CommunalScenes.Communal)
 
-                // Touch events are intercepted outside of any gesture areas.
+                // Touch events are intercepted.
                 assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
                 // User activity sent to PowerManager.
                 verify(powerManager).userActivity(any(), any(), any())
@@ -288,13 +303,12 @@
                 GlanceableHubContainerController(
                     communalInteractor,
                     communalViewModel,
-                    dialogFactory,
-                    keyguardTransitionInteractor,
                     keyguardInteractor,
                     shadeInteractor,
                     powerManager,
                     communalColors,
                     ambientTouchComponentFactory,
+                    communalContent,
                     kosmos.sceneDataSourceDelegator,
                 )
 
@@ -308,13 +322,12 @@
                 GlanceableHubContainerController(
                     communalInteractor,
                     communalViewModel,
-                    dialogFactory,
-                    keyguardTransitionInteractor,
                     keyguardInteractor,
                     shadeInteractor,
                     powerManager,
                     communalColors,
                     ambientTouchComponentFactory,
+                    communalContent,
                     kosmos.sceneDataSourceDelegator,
                 )
 
@@ -501,13 +514,6 @@
     }
 
     private fun goToScene(scene: SceneKey) {
-        if (SceneContainerFlag.isEnabled) {
-            if (scene == CommunalScenes.Communal) {
-                kosmos.sceneInteractor.changeScene(Scenes.Communal, "test")
-            } else {
-                kosmos.sceneInteractor.changeScene(Scenes.Lockscreen, "test")
-            }
-        }
         communalRepository.changeScene(scene)
         testableLooper.processAllMessages()
     }
@@ -536,11 +542,5 @@
             MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, CONTAINER_WIDTH.toFloat(), 0f, 0)
         private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
         private val UP_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
-
-        @JvmStatic
-        @Parameters(name = "{0}")
-        fun getParams(): List<FlagsParameterization> {
-            return FlagsParameterization.allCombinationsOf().andSceneContainer()
-        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 3793970..8e32907 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -446,6 +446,7 @@
                 mUiEventLogger,
                 () -> mKosmos.getInteractionJankMonitor(),
                 mJavaAdapter,
+                () -> mKeyguardTransitionInteractor,
                 () -> mShadeInteractor,
                 () -> mKosmos.getDeviceUnlockedInteractor(),
                 () -> mKosmos.getSceneInteractor(),
@@ -546,6 +547,8 @@
         }).when(mView).setOnTouchListener(any(NotificationPanelViewController.TouchHandler.class));
 
         // Dreaming->Lockscreen
+        when(mKeyguardTransitionInteractor.transition(any()))
+                .thenReturn(emptyFlow());
         when(mKeyguardTransitionInteractor.transition(any(), any()))
                 .thenReturn(emptyFlow());
         when(mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha())
@@ -600,6 +603,7 @@
                                 new UiEventLoggerFake(),
                                 () -> mKosmos.getInteractionJankMonitor(),
                                 mJavaAdapter,
+                                () -> mKeyguardTransitionInteractor,
                                 () -> mShadeInteractor,
                                 () -> mKosmos.getDeviceUnlockedInteractor(),
                                 () -> mKosmos.getSceneInteractor(),
@@ -653,7 +657,7 @@
         when(mView.getParent()).thenReturn(mViewParent);
         when(mQs.getHeader()).thenReturn(mQsHeader);
         when(mDownMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN);
-        when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
+        when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
 
         mMainHandler = new Handler(Looper.getMainLooper());
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 6b57f6e..e1cdda4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -428,6 +428,7 @@
     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 */,
@@ -456,6 +457,7 @@
         // 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
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 112829a..4a867a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -17,12 +17,15 @@
 package com.android.systemui.shade
 
 import android.content.Context
+import android.platform.test.annotations.RequiresFlagsDisabled
 import android.platform.test.flag.junit.FlagsParameterization
+import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
 import android.view.KeyEvent
 import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
+import android.view.ViewTreeObserver
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardSecurityContainerController
 import com.android.keyguard.LegacyLockIconViewController
@@ -43,6 +46,7 @@
 import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -72,7 +76,6 @@
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
-import java.util.Optional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.emptyFlow
@@ -80,12 +83,12 @@
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertEquals
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito.anyFloat
+import org.mockito.Mockito.atLeast
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
@@ -93,13 +96,14 @@
 import org.mockito.MockitoAnnotations
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
 import platform.test.runner.parameterized.Parameters
+import java.util.Optional
 import org.mockito.Mockito.`when` as whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
 @RunWithLooper(setAsMainLooper = true)
-class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : SysuiTestCase() {
 
     @Mock private lateinit var view: NotificationShadeWindowView
     @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
@@ -152,11 +156,12 @@
     private lateinit var underTest: NotificationShadeWindowViewController
 
     private lateinit var testScope: TestScope
+    private lateinit var testableLooper: TestableLooper
 
     private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
@@ -169,7 +174,7 @@
                 .thenReturn(keyguardBouncerComponent)
         whenever(keyguardBouncerComponent.securityContainerController)
                 .thenReturn(keyguardSecurityContainerController)
-        whenever(keyguardTransitionInteractor.transition(LOCKSCREEN, DREAMING))
+        whenever(keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, DREAMING)))
                 .thenReturn(emptyFlow<TransitionStep>())
 
         featureFlagsClassic = FakeFeatureFlagsClassic()
@@ -181,6 +186,7 @@
         mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
 
         testScope = TestScope()
+        testableLooper = TestableLooper.get(this)
         falsingCollector = FalsingCollectorFake()
         fakeClock = FakeSystemClock()
         underTest =
@@ -407,6 +413,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     fun handleDispatchTouchEvent_glanceableHubIntercepts_returnsTrue() {
         whenever(mGlanceableHubContainerController.onTouchEvent(DOWN_EVENT)).thenReturn(true)
         underTest.setStatusBarViewController(phoneStatusBarViewController)
@@ -512,46 +519,6 @@
     }
 
     @Test
-    fun handleExternalTouch_intercepted_sendsOnTouch() {
-        // Accept dispatch and also intercept.
-        whenever(view.dispatchTouchEvent(any())).thenReturn(true)
-        whenever(view.onInterceptTouchEvent(any())).thenReturn(true)
-
-        underTest.handleExternalTouch(DOWN_EVENT)
-        underTest.handleExternalTouch(MOVE_EVENT)
-
-        // Once intercepted, both events are sent to the view.
-        verify(view).onTouchEvent(DOWN_EVENT)
-        verify(view).onTouchEvent(MOVE_EVENT)
-    }
-
-    @Test
-    fun handleExternalTouch_notDispatched_interceptNotCalled() {
-        // Don't accept dispatch
-        whenever(view.dispatchTouchEvent(any())).thenReturn(false)
-
-        underTest.handleExternalTouch(DOWN_EVENT)
-
-        // Interception is not offered.
-        verify(view, never()).onInterceptTouchEvent(any())
-    }
-
-    @Test
-    fun handleExternalTouch_notIntercepted_onTouchNotSent() {
-        // Accept dispatch, but don't dispatch
-        whenever(view.dispatchTouchEvent(any())).thenReturn(true)
-        whenever(view.onInterceptTouchEvent(any())).thenReturn(false)
-
-        underTest.handleExternalTouch(DOWN_EVENT)
-        underTest.handleExternalTouch(MOVE_EVENT)
-
-        // Interception offered for both events, but onTouchEvent is never called.
-        verify(view).onInterceptTouchEvent(DOWN_EVENT)
-        verify(view).onInterceptTouchEvent(MOVE_EVENT)
-        verify(view, never()).onTouchEvent(any())
-    }
-
-    @Test
     fun testGetKeyguardMessageArea() =
         testScope.runTest {
             underTest.keyguardMessageArea
@@ -559,29 +526,42 @@
         }
 
     @Test
-    @Ignore("b/321332798")
+    @DisableSceneContainer
     fun setsUpCommunalHubLayout_whenFlagEnabled() {
         whenever(mGlanceableHubContainerController.communalAvailable())
-                .thenReturn(MutableStateFlow(true))
+            .thenReturn(MutableStateFlow(true))
 
-        val mockCommunalView = mock(View::class.java)
+        val communalView = View(context)
         whenever(mGlanceableHubContainerController.initView(any<Context>()))
-                .thenReturn(mockCommunalView)
+            .thenReturn(communalView)
 
         val mockCommunalPlaceholder = mock(View::class.java)
         val fakeViewIndex = 20
         whenever(view.findViewById<View>(R.id.communal_ui_stub)).thenReturn(mockCommunalPlaceholder)
         whenever(view.indexOfChild(mockCommunalPlaceholder)).thenReturn(fakeViewIndex)
         whenever(view.context).thenReturn(context)
+        whenever(view.viewTreeObserver).thenReturn(mock(ViewTreeObserver::class.java))
 
         underTest.setupCommunalHubLayout()
 
-        // Communal view added as a child of the container at the proper index, the stub is removed.
-        verify(view).removeView(mockCommunalPlaceholder)
-        verify(view).addView(eq(mockCommunalView), eq(fakeViewIndex))
+        // Simluate attaching the view so flow collection starts.
+        val onAttachStateChangeListenerArgumentCaptor = ArgumentCaptor.forClass(
+            View.OnAttachStateChangeListener::class.java
+        )
+        verify(view, atLeast(1)).addOnAttachStateChangeListener(
+            onAttachStateChangeListenerArgumentCaptor.capture()
+        )
+        for (listener in onAttachStateChangeListenerArgumentCaptor.allValues) {
+            listener.onViewAttachedToWindow(view)
+        }
+        testableLooper.processAllMessages()
+
+        // Communal view added as a child of the container at the proper index.
+        verify(view).addView(eq(communalView), eq(fakeViewIndex))
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_COMMUNAL_HUB)
     fun doesNotSetupCommunalHubLayout_whenFlagDisabled() {
         whenever(mGlanceableHubContainerController.communalAvailable())
                 .thenReturn(MutableStateFlow(false))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index f380b6c..e83a46b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.res.R
@@ -151,7 +152,7 @@
         whenever(statusBarStateController.isDozing).thenReturn(false)
         mDependency.injectTestDependency(ShadeController::class.java, shadeController)
         whenever(dockManager.isDocked).thenReturn(false)
-        whenever(keyguardTransitionInteractor.transition(LOCKSCREEN, DREAMING))
+        whenever(keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, DREAMING)))
             .thenReturn(emptyFlow())
 
         val featureFlags = FakeFeatureFlags()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 81d0e06..2c2fcbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.shade
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
@@ -26,8 +28,8 @@
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
+import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.fragments.FragmentHostManager
 import com.android.systemui.fragments.FragmentService
@@ -164,10 +166,10 @@
     }
 
     @Test
+    @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
     fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSetBasedOnResource() {
         val headerResourceHeight = 20
         val headerHelperHeight = 30
-        mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
         whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
             .thenReturn(headerHelperHeight)
         overrideResource(R.bool.config_use_large_screen_shade_header, true)
@@ -187,10 +189,10 @@
     }
 
     @Test
+    @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
     fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSetBasedOnHelper() {
         val headerResourceHeight = 20
         val headerHelperHeight = 30
-        mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
         whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
             .thenReturn(headerHelperHeight)
         overrideResource(R.bool.config_use_large_screen_shade_header, true)
@@ -400,8 +402,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun testSplitShadeLayout_isAlignedToGuideline() {
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
         enableSplitShade()
         underTest.updateResources()
         assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline)
@@ -410,8 +412,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun testSinglePaneLayout_childrenHaveEqualMargins() {
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
         disableSplitShade()
         underTest.updateResources()
         val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
@@ -427,8 +429,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
         enableSplitShade()
         underTest.updateResources()
         assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
@@ -445,9 +447,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
     fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderHeightResource() {
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-        mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
         setLargeScreen()
         val largeScreenHeaderResourceHeight = 100
         val largeScreenHeaderHelperHeight = 200
@@ -468,9 +469,9 @@
     }
 
     @Test
+    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+    @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
     fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHeightHelper() {
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-        mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
         setLargeScreen()
         val largeScreenHeaderResourceHeight = 100
         val largeScreenHeaderHelperHeight = 200
@@ -491,8 +492,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
         setSmallScreen()
         underTest.updateResources()
         assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
@@ -512,8 +513,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun testSinglePaneShadeLayout_isAlignedToParent() {
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
         disableSplitShade()
         underTest.updateResources()
         assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index 4ae751b..f21def3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.shade
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
@@ -27,6 +29,7 @@
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
+import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.fragments.FragmentHostManager
 import com.android.systemui.fragments.FragmentService
@@ -67,6 +70,7 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
+@EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
 class NotificationsQSContainerControllerTest : SysuiTestCase() {
 
     private val view = mock<NotificationsQuickSettingsContainer>()
@@ -99,7 +103,6 @@
         MockitoAnnotations.initMocks(this)
         fakeSystemClock = FakeSystemClock()
         delayableExecutor = FakeExecutor(fakeSystemClock)
-        mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
         mContext.ensureTestableResources()
         whenever(view.context).thenReturn(mContext)
         whenever(view.resources).thenReturn(mContext.resources)
@@ -161,8 +164,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
     fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSet_basedOnResource() {
-        mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
         val helperHeight = 30
         val resourceHeight = 20
         whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight)
@@ -182,8 +185,8 @@
     }
 
     @Test
+    @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
     fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSet_basedOnHelper() {
-        mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
         val helperHeight = 30
         val resourceHeight = 20
         whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight)
@@ -424,8 +427,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
     fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderResourceHeight() {
-        mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
         setLargeScreen()
         val largeScreenHeaderHelperHeight = 200
         val largeScreenHeaderResourceHeight = 100
@@ -444,8 +447,8 @@
     }
 
     @Test
+    @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
     fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHelperHeight() {
-        mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
         setLargeScreen()
         val largeScreenHeaderHelperHeight = 200
         val largeScreenHeaderResourceHeight = 100
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 04fa590..845744a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -42,13 +42,10 @@
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlagsClassic;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
@@ -59,9 +56,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.qs.QSFragmentLegacy;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.data.repository.SceneContainerRepository;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.logger.SceneLogger;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.shade.data.repository.FakeShadeRepository;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
@@ -176,12 +171,7 @@
     protected Handler mMainHandler;
     protected LockscreenShadeTransitionController.Callback mLockscreenShadeTransitionCallback;
 
-    protected final ShadeExpansionStateManager mShadeExpansionStateManager =
-            new ShadeExpansionStateManager();
-
     protected FragmentHostManager.FragmentListener mFragmentListener;
-    private FromLockscreenTransitionInteractor mFromLockscreenTransitionInteractor;
-    private FromPrimaryBouncerTransitionInteractor mFromPrimaryBouncerTransitionInteractor;
 
     @Before
     public void setup() {
@@ -190,19 +180,11 @@
         mStatusBarStateController = mKosmos.getStatusBarStateController();
 
         mKosmos.getFakeDeviceProvisioningRepository().setDeviceProvisioned(true);
-        FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
         FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
 
         PowerInteractor powerInteractor = mKosmos.getPowerInteractor();
 
-        SceneInteractor sceneInteractor = new SceneInteractor(
-                mTestScope.getBackgroundScope(),
-                new SceneContainerRepository(
-                        mTestScope.getBackgroundScope(),
-                        mKosmos.getFakeSceneContainerConfig(),
-                        mKosmos.getSceneDataSource()),
-                mock(SceneLogger.class),
-                mKosmos.getDeviceUnlockedInteractor());
+        SceneInteractor sceneInteractor = mKosmos.getSceneInteractor();
 
         KeyguardTransitionInteractor keyguardTransitionInteractor =
                 mKosmos.getKeyguardTransitionInteractor();
@@ -220,10 +202,6 @@
                 () -> mKosmos.getSharedNotificationContainerInteractor(),
                 mTestScope);
 
-        mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
-        mFromPrimaryBouncerTransitionInteractor =
-                mKosmos.getFromPrimaryBouncerTransitionInteractor();
-
         ResourcesSplitShadeStateController splitShadeStateController =
                 new ResourcesSplitShadeStateController();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
index 2c453a7..dfd7a71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancelChildren
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -36,6 +37,8 @@
             runCurrent()
 
             assertThat(mQsController.isExpansionEnabled).isFalse()
+
+            coroutineContext.cancelChildren()
         }
 
     @Test
@@ -45,6 +48,8 @@
             runCurrent()
 
             assertThat(mQsController.isExpansionEnabled).isTrue()
+
+            coroutineContext.cancelChildren()
         }
 
     @Test
@@ -58,6 +63,8 @@
             runCurrent()
 
             assertThat(mQsController.isExpansionEnabled).isFalse()
+
+            coroutineContext.cancelChildren()
         }
 
     @Test
@@ -71,6 +78,8 @@
             runCurrent()
 
             assertThat(mQsController.isExpansionEnabled).isFalse()
+
+            coroutineContext.cancelChildren()
         }
 
     @Test
@@ -81,5 +90,7 @@
             runCurrent()
 
             assertThat(mQsController.isExpansionEnabled).isTrue()
+
+            coroutineContext.cancelChildren()
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt
index 9ec9b69..05d9495 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt
@@ -17,8 +17,8 @@
 
 package com.android.systemui.statusbar
 
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.ExpandHelper
 import com.android.systemui.SysuiTestCase
@@ -39,7 +39,7 @@
 
 @SmallTest
 @TestableLooper.RunWithLooper
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class DragDownHelperTest : SysuiTestCase() {
 
     private lateinit var dragDownHelper: DragDownHelper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 1504d4c..995b538 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -65,9 +65,9 @@
 import android.hardware.biometrics.BiometricSourceType;
 import android.os.BatteryManager;
 import android.os.RemoteException;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
@@ -88,7 +88,7 @@
 import java.util.Set;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class KeyguardIndicationControllerTest extends KeyguardIndicationControllerBaseTest {
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt
index cdc7520..4a14f88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar
 
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
 import kotlinx.coroutines.Dispatchers
@@ -28,7 +28,7 @@
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class KeyguardIndicationControllerWithCoroutinesTest : KeyguardIndicationControllerBaseTest() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
index 8cb530c..948a732 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
@@ -1,7 +1,7 @@
 package com.android.systemui.statusbar
 
-import android.testing.AndroidTestingRunner
 import android.util.DisplayMetrics
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.LogBuffer
@@ -16,7 +16,7 @@
 import org.mockito.Mock
 import org.mockito.junit.MockitoJUnit
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class LSShadeTransitionLoggerTest : SysuiTestCase() {
     lateinit var logger: LSShadeTransitionLogger
@@ -41,4 +41,4 @@
         // log a non-null, non row, ensure no crash
         logger.logDragDownStarted(view)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
index d3befb4..fe2dd6d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
@@ -16,8 +16,8 @@
 
 package com.android.systemui.statusbar
 
-import android.testing.AndroidTestingRunner
 import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
@@ -28,7 +28,7 @@
 import org.junit.runner.RunWith
 import java.util.function.Consumer
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class LightRevealScrimTest : SysuiTestCase() {
 
@@ -85,4 +85,4 @@
     private const val DEFAULT_WIDTH = 42
     private const val DEFAULT_HEIGHT = 24
   }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt
index 402d9aa..e48242a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
@@ -36,7 +36,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class LockscreenShadeQsTransitionControllerTest : SysuiTestCase() {
 
     private val configurationController = FakeConfigurationController()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index a92cf8c..69e8f47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -1,9 +1,9 @@
 package com.android.systemui.statusbar
 
 import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
-import android.testing.AndroidTestingRunner
 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.ExpandHelper
 import com.android.systemui.SysUITestModule
@@ -74,7 +74,7 @@
 
 @SmallTest
 @RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalCoroutinesApi::class)
 class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index d3febf5..ef1c927 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -32,8 +32,8 @@
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -52,7 +52,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class NotificationListenerTest extends SysuiTestCase {
     private static final String TEST_PACKAGE_NAME = "test";
     private static final int TEST_UID = 0;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index d3850be..c9d910c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -27,9 +27,9 @@
 import android.content.Context;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -52,7 +52,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class NotificationRemoteInputManagerTest extends SysuiTestCase {
     private static final String TEST_PACKAGE_NAME = "test";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index fc0c85e..9f94cff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -17,11 +17,11 @@
 package com.android.systemui.statusbar
 
 import android.os.IBinder
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.Choreographer
 import android.view.View
 import android.view.ViewRootImpl
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ShadeInterpolation
@@ -59,7 +59,7 @@
 import org.mockito.Mockito.`when`
 import org.mockito.junit.MockitoJUnit
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 @SmallTest
 class NotificationShadeDepthControllerTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
index 49e5c45..9907740 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
@@ -17,8 +17,8 @@
 
 package com.android.systemui.statusbar
 
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
@@ -43,7 +43,7 @@
 
 @SmallTest
 @TestableLooper.RunWithLooper
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class PulseExpansionHandlerTest : SysuiTestCase() {
 
     private lateinit var pulseExpansionHandler: PulseExpansionHandler
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java
index ce11d6a..58943ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java
@@ -27,9 +27,9 @@
 import android.net.Uri;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -44,7 +44,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class RemoteInputNotificationRebuilderTest extends SysuiTestCase {
     private static final String TEST_PACKAGE_NAME = "test";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt
index 2606be5..6b9a19a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt
@@ -1,7 +1,7 @@
 package com.android.systemui.statusbar
 
 import org.mockito.Mockito.`when` as whenever
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -14,7 +14,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class SingleShadeLockScreenOverScrollerTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
index 775dc3c..3346e19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
@@ -29,9 +29,9 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -52,7 +52,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 @SmallTest
 public class SmartReplyControllerTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
index 700fb1e..58473c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
@@ -1,7 +1,7 @@
 package com.android.systemui.statusbar
 
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
@@ -23,7 +23,7 @@
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 @TestableLooper.RunWithLooper
 class SplitShadeLockScreenOverScrollerTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt
index 79a2008..26692c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import org.junit.Assert.assertEquals
@@ -24,7 +24,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class StatusBarStateEventTest : SysuiTestCase() {
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
index b905825..78c1887 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
@@ -5,9 +5,9 @@
 import android.os.VibrationAttributes
 import android.os.VibrationEffect
 import android.os.Vibrator
-import android.testing.AndroidTestingRunner
 import android.view.HapticFeedbackConstants
 import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.eq
@@ -26,7 +26,7 @@
 import org.mockito.junit.MockitoJUnit
 import java.util.concurrent.Executor
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class VibratorHelperTest : SysuiTestCase() {
 
@@ -120,4 +120,4 @@
 
         return verify(vibrator)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt
similarity index 86%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt
index f0a457e..7d2b463 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone.ongoingcall
+package com.android.systemui.statusbar.chips.ui.view
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.LayoutInflater
 import android.view.View
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -31,16 +31,17 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
-class OngoingCallBackgroundContainerTest : SysuiTestCase() {
+class ChipBackgroundContainerTest : SysuiTestCase() {
 
-    private lateinit var underTest: OngoingCallBackgroundContainer
+    private lateinit var underTest: ChipBackgroundContainer
 
     @Before
     fun setUp() {
         allowTestableLooperAsMainThread()
         TestableLooper.get(this).runWithLooper {
-            val chipView = LayoutInflater.from(context).inflate(R.layout.ongoing_call_chip, null)
-            underTest = chipView.requireViewById(R.id.ongoing_call_chip_background)
+            val chipView =
+                LayoutInflater.from(context).inflate(R.layout.ongoing_activity_chip, null)
+            underTest = chipView.requireViewById(R.id.ongoing_activity_chip_background)
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt
similarity index 89%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt
index 7e25aa3..b8d4e47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone.ongoingcall
+package com.android.systemui.statusbar.chips.ui.view
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.LayoutInflater
 import android.view.View
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -38,17 +38,18 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
-class OngoingCallChronometerTest : SysuiTestCase() {
+class ChipChronometerTest : SysuiTestCase() {
 
-    private lateinit var textView: OngoingCallChronometer
+    private lateinit var textView: ChipChronometer
     private lateinit var doesNotFitText: String
 
     @Before
     fun setUp() {
         allowTestableLooperAsMainThread()
         TestableLooper.get(this).runWithLooper {
-            val chipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_call_chip, null)
-            textView = chipView.findViewById(R.id.ongoing_call_chip_time)!!
+            val chipView =
+                LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip, null)
+            textView = chipView.findViewById(R.id.ongoing_activity_chip_time)!!
             measureTextView()
             calculateDoesNotFixText()
         }
@@ -159,8 +160,8 @@
 
     private fun measureTextView() {
         textView.measure(
-                View.MeasureSpec.makeMeasureSpec(TEXT_VIEW_MAX_WIDTH, View.MeasureSpec.AT_MOST),
-                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+            View.MeasureSpec.makeMeasureSpec(TEXT_VIEW_MAX_WIDTH, View.MeasureSpec.AT_MOST),
+            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
index 7e88ae0..643acdb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.connectivity
 
 import android.os.UserManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import androidx.lifecycle.Lifecycle
 import com.android.systemui.SysuiTestCase
@@ -42,7 +42,7 @@
 import java.util.concurrent.Executor
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper(setAsMainLooper = true)
 class AccessPointControllerImplTest : SysuiTestCase() {
 
@@ -244,4 +244,4 @@
         verify(wifiEntryOther).connect(any())
         verify(callback, never()).onSettingsActivityTriggered(any())
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt
index 7aed4f7..40f81e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt
@@ -15,7 +15,7 @@
  */
 package com.android.systemui.statusbar.connectivity
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.SysuiTestCase
@@ -26,7 +26,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class MobileStateTest : SysuiTestCase() {
 
     private val state = MobileState()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index 461d804..4241254 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -39,10 +39,10 @@
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.settingslib.SignalIcon.MobileIconGroup;
@@ -60,7 +60,7 @@
 import java.util.HashMap;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @RunWithLooper
 public class NetworkControllerDataTest extends NetworkControllerBaseTest {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
index 3bbf06d..521cb4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
@@ -19,9 +19,9 @@
 import static junit.framework.Assert.assertEquals;
 
 import android.net.NetworkCapabilities;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
@@ -30,7 +30,7 @@
 import org.mockito.Mockito;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @RunWithLooper
 public class NetworkControllerEthernetTest extends NetworkControllerBaseTest {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index 35609a5..22f0e9d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -33,10 +33,10 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.settingslib.graph.SignalDrawable;
@@ -59,7 +59,7 @@
 import java.util.List;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @RunWithLooper
 public class NetworkControllerSignalTest extends NetworkControllerBaseTest {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
index 44a1c50..6c80a97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
@@ -34,9 +34,9 @@
 import android.net.vcn.VcnTransportInfo;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.settingslib.mobile.TelephonyIcons;
@@ -50,7 +50,7 @@
 import java.util.Collections;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @RunWithLooper
 public class NetworkControllerWifiTest extends NetworkControllerBaseTest {
     // These match the constants in WifiManager and need to be kept up to date.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
index 5bf0a94..3eeed73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.connectivity
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.systemui.SysuiTestCase
@@ -26,7 +26,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class NetworkTypeResIdCacheTest : SysuiTestCase() {
     private lateinit var cache: NetworkTypeResIdCache
     private var overrides = MobileIconCarrierIdOverridesFake()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
index 452302d..984bda1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
@@ -19,11 +19,11 @@
 import android.content.Context
 import android.graphics.Insets
 import android.graphics.Rect
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.Gravity
 import android.view.View
 import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.AnimatorTestRule
@@ -46,7 +46,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class SystemEventChipAnimationControllerTest : SysuiTestCase() {
     private lateinit var controller: SystemEventChipAnimationController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
index ae84df5..742494b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
@@ -15,8 +15,8 @@
  */
 package com.android.systemui.statusbar.events
 
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
@@ -40,7 +40,7 @@
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.MockitoAnnotations
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 @OptIn(ExperimentalCoroutinesApi::class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index cacfa8d..376873d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -19,10 +19,10 @@
 import android.graphics.Insets
 import android.graphics.Rect
 import android.os.Process
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.View
 import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.AnimatorTestRule
@@ -54,7 +54,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper(setAsMainLooper = true)
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt
index d3f5ade..0f58990 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt
@@ -1,9 +1,9 @@
 package com.android.systemui.statusbar.gesture
 
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.InputEvent
 import android.view.MotionEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.settings.FakeDisplayTracker
@@ -13,7 +13,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class GenericGestureDetectorTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
index 6b2ee76..01a0fd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
@@ -19,11 +19,11 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.widget.FrameLayout;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -36,7 +36,7 @@
 import org.junit.runner.RunWith;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @RunWithLooper
 public class AboveShelfObserverTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
index fc4702c..d66b010 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
@@ -42,9 +42,9 @@
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
@@ -58,7 +58,7 @@
 import org.junit.runner.RunWith;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class AssistantFeedbackControllerTest extends SysuiTestCase {
     private static final String TEST_PACKAGE_NAME = "test_package";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
index 0103564..77fd067 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
@@ -25,12 +25,12 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.ArrayMap;
 import android.view.LayoutInflater;
 import android.view.View;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -56,7 +56,7 @@
 import java.util.Map;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class DynamicChildBindControllerTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
index 5b72ca0..d879fce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
@@ -25,9 +25,9 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -38,9 +38,10 @@
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 @SmallTest
-@org.junit.runner.RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class DynamicPrivacyControllerTest extends SysuiTestCase {
 
@@ -127,4 +128,4 @@
         mDynamicPrivacyController.onUnlockedChanged();
         verifyNoMoreInteractions(mListener);
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
index 1cce3b5..8e8a351 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
@@ -18,8 +18,8 @@
 
 import android.provider.DeviceConfig
 import android.provider.Settings
-import android.testing.AndroidTestingRunner
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.dx.mockito.inline.extended.ExtendedMockito
 
@@ -39,7 +39,7 @@
 import org.mockito.MockitoSession
 import org.mockito.quality.Strictness
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class NotificationSectionsFeatureManagerTest : SysuiTestCase() {
     var manager: NotificationSectionsFeatureManager? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
index 3b3f05d..3abdf62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
@@ -1,9 +1,9 @@
 package com.android.systemui.statusbar.notification
 
 import android.app.Notification.GROUP_ALERT_SUMMARY
-import android.testing.AndroidTestingRunner
 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.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
@@ -35,7 +35,7 @@
 import org.mockito.junit.MockitoJUnit
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class NotificationTransitionAnimatorControllerTest : SysuiTestCase() {
     @Mock lateinit var notificationListContainer: NotificationListContainer
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
index 1aac515..a5206f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.notification
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.LogBuffer
@@ -28,7 +28,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class NotificationWakeUpCoordinatorLoggerTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
index 67b540c..0906d8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
@@ -16,8 +16,8 @@
 
 package com.android.systemui.statusbar.notification
 
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.SysuiTestCase
@@ -60,7 +60,7 @@
 import org.mockito.Mockito.verifyNoMoreInteractions
 
 @OptIn(ExperimentalCoroutinesApi::class)
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
index 7d8cf36..382b307 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
@@ -2,6 +2,7 @@
 
 import android.platform.test.annotations.EnableFlags
 import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
@@ -10,13 +11,12 @@
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class RoundableTest : SysuiTestCase() {
     private val targetView: View = mock()
     private val roundable = FakeRoundable(targetView = targetView)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
index 2d044fe..8e95ac5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
@@ -30,8 +30,8 @@
 
 import android.app.Notification;
 import android.app.NotificationChannel;
-import android.testing.AndroidTestingRunner;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -51,7 +51,7 @@
 import java.util.List;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class HighPriorityProviderTest extends SysuiTestCase {
     @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
     @Mock private GroupMembershipManager mGroupMembershipManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt
index 892575a..2a58751 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.notification.collection
 
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import androidx.lifecycle.Observer
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -34,7 +34,7 @@
 import org.mockito.Mockito.verifyNoMoreInteractions
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class NotifLiveDataImplTest : SysuiTestCase() {
 
@@ -164,4 +164,4 @@
         assertThat(executor.runAllReady()).isEqualTo(2)
         verifyNoMoreInteractions(syncObserver, asyncObserver)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt
index 9c8ac5c..d87f827 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt
@@ -16,8 +16,8 @@
 
 package com.android.systemui.statusbar.notification.collection
 
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -30,7 +30,7 @@
 import java.lang.UnsupportedOperationException
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class NotifLiveDataStoreImplTest : SysuiTestCase() {
 
@@ -102,4 +102,4 @@
         liveDataStoreImpl.setActiveNotifList(mutableListOf(entry1, entry2))
         executor.runAllReady()
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt
index 3b908b4..f1da22f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt
@@ -16,8 +16,8 @@
 
 package com.android.systemui.statusbar.notification.collection
 
-import android.testing.AndroidTestingRunner
 import android.view.Choreographer
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dagger.SysUISingleton
@@ -36,7 +36,7 @@
 import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class NotifPipelineChoreographerTest : SysuiTestCase() {
 
     val viewChoreographer: Choreographer = mock()
@@ -118,4 +118,4 @@
             @BindsInstance @Main executor: DelayableExecutor
         ): NotifPipelineChoreographerTestComponent
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index 8a48fe1..72d1db3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -46,8 +46,8 @@
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -64,7 +64,7 @@
 import java.util.ArrayList;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class NotificationEntryTest extends SysuiTestCase {
     private static final String TEST_PACKAGE_NAME = "test";
     private static final int TEST_UID = 0;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/SectionStyleProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/SectionStyleProviderTest.kt
index ab55a7d..1fd6b04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/SectionStyleProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/SectionStyleProviderTest.kt
@@ -20,7 +20,7 @@
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
 import android.service.notification.StatusBarNotification
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
@@ -47,7 +47,7 @@
 import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class SectionStyleProviderTest : SysuiTestCase() {
 
     @Rule @JvmField public val setFlagsRule = SetFlagsRule()
@@ -118,4 +118,4 @@
             override fun getSection(): NotifSection? = NotifSection(inputSectioner, 1)
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
index 4708350..2ad3c9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
@@ -25,7 +25,7 @@
 import android.os.UserHandle
 import android.service.notification.NotificationListenerService.Ranking
 import android.service.notification.StatusBarNotification
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
@@ -48,7 +48,7 @@
 private const val USER_ID = -1
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class TargetSdkResolverTest : SysuiTestCase() {
     private val packageManager: PackageManager = mock()
     private val applicationInfo = ApplicationInfo().apply { targetSdkVersion = SDK_VERSION }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
index b1180ae..f029a2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
@@ -30,8 +30,8 @@
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -57,7 +57,7 @@
 import java.util.Collections;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class GroupCoalescerTest extends SysuiTestCase {
 
     private GroupCoalescer mCoalescer;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
index f2207af..1f29255 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
@@ -30,9 +30,9 @@
 import android.content.Intent;
 import android.graphics.Color;
 import android.os.UserHandle;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -47,7 +47,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class ColorizedFgsCoordinatorTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt
index 59fc591..e72109d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt
@@ -15,8 +15,8 @@
  */
 package com.android.systemui.statusbar.notification.collection.coordinator
 
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
@@ -39,7 +39,7 @@
 import org.mockito.MockitoAnnotations.initMocks
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class DataStoreCoordinatorTest : SysuiTestCase() {
     private lateinit var coordinator: DataStoreCoordinator
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt
index f91e5a8..543f0c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import android.app.Notification
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
@@ -38,7 +38,7 @@
 import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class DismissibilityCoordinatorTest : SysuiTestCase() {
 
     private lateinit var coordinator: DismissibilityCoordinator
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt
index a544cad..4d5ea92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt
@@ -17,7 +17,7 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -49,7 +49,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class DreamCoordinatorTest : SysuiTestCase() {
     @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
     @Mock private lateinit var notifPipeline: NotifPipeline
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt
index 929c3d4..7b688d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt
@@ -15,8 +15,8 @@
  */
 package com.android.systemui.statusbar.notification.collection.coordinator
 
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
@@ -36,7 +36,7 @@
 import org.mockito.MockitoAnnotations.initMocks
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class GroupCountCoordinatorTest : SysuiTestCase() {
     private lateinit var coordinator: GroupCountCoordinator
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt
index eac0e29..3f14026 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt
@@ -16,8 +16,8 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import android.app.Notification
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.SbnBuilder
@@ -45,7 +45,7 @@
 import org.mockito.MockitoAnnotations.initMocks
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class GroupWhenCoordinatorTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt
index a652ad6..7fe97d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt
@@ -15,8 +15,8 @@
  */
 package com.android.systemui.statusbar.notification.collection.coordinator
 
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
@@ -42,7 +42,7 @@
 import org.mockito.MockitoAnnotations.initMocks
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class GutsCoordinatorTest : SysuiTestCase() {
     private lateinit var coordinator: GutsCoordinator
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index cd75e08..8e9323f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -17,8 +17,8 @@
 
 import android.app.Notification.GROUP_ALERT_ALL
 import android.app.Notification.GROUP_ALERT_SUMMARY
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.logcatLogBuffer
@@ -70,7 +70,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class HeadsUpCoordinatorTest : SysuiTestCase() {
     private lateinit var coordinator: HeadsUpCoordinator
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java
index 27542a4..5dcad4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java
@@ -24,9 +24,9 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.testing.AndroidTestingRunner;
 import android.util.SparseArray;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -47,7 +47,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class HideNotifsForOtherUsersCoordinatorTest extends SysuiTestCase {
 
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 5ff7353..25533d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -20,7 +20,7 @@
 import android.app.Notification
 import android.os.UserHandle
 import android.provider.Settings
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
@@ -67,7 +67,7 @@
 import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class KeyguardCoordinatorTest : SysuiTestCase() {
 
     private val headsUpManager: HeadsUpManager = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
index e90a3ac8..07c29a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
@@ -33,8 +33,8 @@
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.service.notification.NotificationListenerService;
-import android.testing.AndroidTestingRunner;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.statusbar.IStatusBarService;
@@ -58,7 +58,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public final class MediaCoordinatorTest extends SysuiTestCase {
 
     private MediaSession mMediaSession;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt
index c29ff41..501bca2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt
@@ -18,7 +18,7 @@
 
 import android.platform.test.annotations.EnableFlags
 import android.service.notification.NotificationListenerService.REASON_CANCEL
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -36,7 +36,7 @@
 import org.mockito.Mockito.verify
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
 class NotificationStatsLoggerCoordinatorTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index cceaaea..8012768 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -39,10 +39,10 @@
 import android.database.ContentObserver;
 import android.os.Handler;
 import android.os.RemoteException;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
 import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.statusbar.IStatusBarService;
@@ -88,7 +88,7 @@
 import java.util.Map;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class PreparationCoordinatorTest extends SysuiTestCase {
     private NotifCollectionListener mCollectionListener;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
index 3d1253e..c05b131 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
@@ -35,9 +35,9 @@
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
-import android.testing.AndroidTestingRunner;
 
 import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -67,7 +67,7 @@
 import java.util.Arrays;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class RankingCoordinatorTest extends SysuiTestCase {
 
     @Mock private StatusBarStateController mStatusBarStateController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
index d3df48e9..deb3fc1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
@@ -23,8 +23,8 @@
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.service.notification.StatusBarNotification
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
@@ -54,7 +54,7 @@
 import org.mockito.MockitoAnnotations.initMocks
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class RemoteInputCoordinatorTest : SysuiTestCase() {
     private lateinit var coordinator: RemoteInputCoordinator
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt
index 7daadb0..1b7ec53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt
@@ -15,8 +15,8 @@
  */
 package com.android.systemui.statusbar.notification.collection.coordinator
 
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
@@ -37,7 +37,7 @@
 import org.mockito.MockitoAnnotations.initMocks
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class RowAlertTimeCoordinatorTest : SysuiTestCase() {
     private lateinit var coordinator: RowAlertTimeCoordinator
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt
index a66f8ce..5b231e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt
@@ -15,8 +15,8 @@
  */
 package com.android.systemui.statusbar.notification.collection.coordinator
 
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.AssistantFeedbackController
@@ -41,7 +41,7 @@
 import org.mockito.MockitoAnnotations.initMocks
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class RowAppearanceCoordinatorTest : SysuiTestCase() {
     private lateinit var coordinator: RowAppearanceCoordinator
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt
index 56f16f3..ccf7cdd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt
@@ -17,8 +17,8 @@
 
 import android.service.notification.NotificationListenerService.REASON_APP_CANCEL
 import android.service.notification.NotificationListenerService.REASON_CANCEL
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.logcatLogBuffer
@@ -40,7 +40,7 @@
 import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class ShadeEventCoordinatorTest : SysuiTestCase() {
     private lateinit var coordinator: ShadeEventCoordinator
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index ea4f692..c7513de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -17,8 +17,8 @@
 
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
 import com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX
@@ -51,7 +51,7 @@
 import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class StackCoordinatorTest : SysuiTestCase() {
     private lateinit var coordinator: StackCoordinator
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt
index b1d2ea21..c8fbe61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt
@@ -15,8 +15,8 @@
  */
 package com.android.systemui.statusbar.notification.collection.coordinator
 
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
@@ -40,7 +40,7 @@
 import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class ViewConfigCoordinatorTest : SysuiTestCase() {
     private lateinit var coordinator: ViewConfigCoordinator
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
index 8e6cecc..7943872 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
@@ -20,8 +20,8 @@
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
 import com.android.systemui.SysuiTestCase
@@ -54,7 +54,7 @@
 import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class NotifUiAdjustmentProviderTest : SysuiTestCase() {
     private val lockscreenUserManager: NotificationLockscreenUserManager = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt
index 1cdd023..d205770 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt
@@ -15,9 +15,9 @@
  */
 package com.android.systemui.statusbar.notification.collection.listbuilder
 
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.util.Log
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import org.junit.Assert.assertFalse
@@ -27,7 +27,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class SemiStableSortTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt
index 2036954..49f836f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt
@@ -15,8 +15,8 @@
  */
 package com.android.systemui.statusbar.notification.collection.listbuilder
 
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderHelper.getContiguousSubLists
@@ -25,7 +25,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class ShadeListBuilderHelperTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt
index 22f6bdc..341a51e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.notification.collection.notifcollection
 
 import android.service.notification.NotificationListenerService.RankingMap
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.logcatLogBuffer
@@ -34,7 +34,7 @@
 import org.mockito.Mockito.verifyNoMoreInteractions
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class NotifCollectionInconsistencyTrackerTest : SysuiTestCase() {
     private val logger = spy(NotifCollectionLogger(logcatLogBuffer()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt
index a09f3a3..99e55a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt
@@ -16,8 +16,8 @@
 package com.android.systemui.statusbar.notification.collection.notifcollection
 
 import android.os.Handler
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -41,7 +41,7 @@
 import java.util.function.Predicate
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class SelfTrackingLifetimeExtenderTest : SysuiTestCase() {
     private lateinit var extender: TestableSelfTrackingLifetimeExtender
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt
index b56f8e9..586b947 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt
@@ -15,7 +15,7 @@
  */
 package com.android.systemui.statusbar.notification.collection.provider
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.mock
@@ -29,7 +29,7 @@
 import org.mockito.Mockito.verifyNoMoreInteractions
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class VisualStabilityProviderTest : SysuiTestCase() {
     private val visualStabilityProvider = VisualStabilityProvider()
     private val listener: OnReorderingAllowedListener = mock()
@@ -148,4 +148,4 @@
         visualStabilityProvider.isReorderingAllowed = true
         verify(selfAddingListener, times(2)).onReorderingAllowed()
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
index eeabc74..9d3e2e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
@@ -16,10 +16,10 @@
 package com.android.systemui.statusbar.notification.collection.render
 
 import android.content.Context
-import android.testing.AndroidTestingRunner
 import android.view.View
 import android.view.ViewGroup
 import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.logcatLogBuffer
@@ -34,7 +34,7 @@
 import org.mockito.Mockito.verify
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class ShadeViewDifferTest : SysuiTestCase() {
     private lateinit var differ: ShadeViewDiffer
     private val rootController = FakeController(mContext, "RootController")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
index 2a3c1a5..3908529 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
@@ -15,7 +15,7 @@
 
 package com.android.systemui.statusbar.notification.domain.interactor
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
@@ -25,7 +25,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class SeenNotificationsInteractorTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 158f38d..83ad18b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -19,12 +19,13 @@
 package com.android.systemui.statusbar.notification.footer.ui.viewmodel
 
 import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
 import android.provider.Settings
-import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -33,7 +34,7 @@
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.power.shared.model.WakefulnessState
 import com.android.systemui.res.R
-import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRepository
 import com.android.systemui.statusbar.notification.collection.render.NotifStats
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
@@ -45,13 +46,16 @@
 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
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 @SmallTest
 @EnableFlags(FooterViewRefactor.FLAG_NAME)
-class FooterViewModelTest : SysuiTestCase() {
+class FooterViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
             fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
@@ -59,11 +63,29 @@
     private val testScope = kosmos.testScope
     private val activeNotificationListRepository = kosmos.activeNotificationListRepository
     private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
-    private val shadeRepository = kosmos.shadeRepository
     private val powerRepository = kosmos.powerRepository
     private val fakeSecureSettingsRepository = kosmos.fakeSecureSettingsRepository
 
-    val underTest = kosmos.footerViewModel
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+    private lateinit var underTest: FooterViewModel
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
+    @Before
+    fun setup() {
+        underTest = kosmos.footerViewModel
+    }
 
     @Test
     fun messageVisible_whenFilteredNotifications() =
@@ -146,11 +168,9 @@
             val visible by collectLastValue(underTest.clearAllButton.isVisible)
             runCurrent()
 
-            // WHEN shade is expanded
+            // WHEN shade is expanded AND QS not expanded
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            shadeRepository.setLegacyShadeExpansion(1f)
-            // AND QS not expanded
-            shadeRepository.setQsExpansion(0f)
+            shadeTestUtil.setShadeAndQsExpansion(1f, 0f)
             // AND device is awake
             powerRepository.updateWakefulness(
                 rawState = WakefulnessState.AWAKE,
@@ -182,9 +202,9 @@
 
             // WHEN shade is collapsed
             fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeTestUtil.setShadeExpansion(0f)
             // AND QS not expanded
-            shadeRepository.setQsExpansion(0f)
+            shadeTestUtil.setQsExpansion(0f)
             // AND device is awake
             powerRepository.updateWakefulness(
                 rawState = WakefulnessState.AWAKE,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
index 4ac9dc2..bfa816e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
@@ -30,8 +30,8 @@
 import android.os.Bundle
 import android.os.SystemClock
 import android.os.UserHandle
-import android.testing.AndroidTestingRunner
 import androidx.test.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapperTest.Companion.any
@@ -53,7 +53,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class IconManagerTest : SysuiTestCase() {
     companion object {
         private const val TEST_PACKAGE_NAME = "test"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
index b410b33..c9f2add 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -15,7 +15,7 @@
 
 package com.android.systemui.statusbar.notification.icon.domain.interactor
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysUITestComponent
 import com.android.systemui.SysUITestModule
@@ -52,7 +52,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class NotificationIconsInteractorTest : SysuiTestCase() {
 
     private val bubbles: Bubbles = mock()
@@ -151,7 +151,7 @@
 }
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
 
     private val bubbles: Bubbles = mock()
@@ -256,7 +256,7 @@
 }
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class StatusBarNotificationIconsInteractorTest : SysuiTestCase() {
 
     private val bubbles: Bubbles = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
index 60eea9b..af2789b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
@@ -26,9 +26,9 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
-import android.testing.AndroidTestingRunner;
 
 import androidx.core.os.CancellationSignal;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.NotificationMessagingUtil;
@@ -47,7 +47,7 @@
 
 import java.util.concurrent.atomic.AtomicReference;
 
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @SmallTest
 public class HeadsUpViewBinderTest extends SysuiTestCase {
     private HeadsUpViewBinder mViewBinder;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
index 8662048..19214fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -42,9 +42,9 @@
 import android.os.Handler;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
@@ -86,7 +86,7 @@
 import java.util.function.Consumer;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class KeyguardNotificationVisibilityProviderTest  extends SysuiTestCase {
     private static final int NOTIF_USER_ID = 0;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 7ade053..3e8461a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -60,8 +60,8 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.platform.test.annotations.DisableFlags;
-import android.testing.AndroidTestingRunner;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.testing.UiEventLoggerFake;
@@ -96,7 +96,7 @@
  * Tests for the interruption state provider which understands whether the system & notification
  * is in a state allowing a particular notification to hun, pulse, or bubble.
  */
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @SmallTest
 public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
index 7ed3312..a6177e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.notification.interruption
 
 import android.platform.test.annotations.DisableFlags
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
@@ -34,7 +34,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
 class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecisionProviderTestBase() {
     override val provider by lazy {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
index edab9d9..eeb51a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -16,11 +16,13 @@
 
 package com.android.systemui.statusbar.notification.interruption
 
+import android.Manifest.permission
 import android.app.Notification.CATEGORY_EVENT
 import android.app.Notification.CATEGORY_REMINDER
 import android.app.NotificationManager
+import android.content.pm.PackageManager.PERMISSION_GRANTED
 import android.platform.test.annotations.EnableFlags
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE
@@ -28,9 +30,11 @@
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.`when`
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
 class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionProviderTestBase() {
     override val provider by lazy {
@@ -51,7 +55,8 @@
             uiEventLogger,
             userTracker,
             avalancheProvider,
-            systemSettings
+            systemSettings,
+            packageManager
         )
     }
 
@@ -83,14 +88,18 @@
     fun testAvalancheFilter_duringAvalanche_allowConversationFromAfterEvent() {
         avalancheProvider.startTime = whenAgo(10)
 
-        withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) {
+        withFilter(
+            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+        ) {
             ensurePeekState()
-            assertShouldHeadsUp(buildEntry {
-                importance = NotificationManager.IMPORTANCE_HIGH
-                isConversation = true
-                isImportantConversation = false
-                whenMs = whenAgo(5)
-            })
+            assertShouldHeadsUp(
+                buildEntry {
+                    importance = NotificationManager.IMPORTANCE_HIGH
+                    isConversation = true
+                    isImportantConversation = false
+                    whenMs = whenAgo(5)
+                }
+            )
         }
     }
 
@@ -98,14 +107,18 @@
     fun testAvalancheFilter_duringAvalanche_suppressConversationFromBeforeEvent() {
         avalancheProvider.startTime = whenAgo(10)
 
-        withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) {
+        withFilter(
+            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+        ) {
             ensurePeekState()
-            assertShouldNotHeadsUp(buildEntry {
-                importance = NotificationManager.IMPORTANCE_DEFAULT
-                isConversation = true
-                isImportantConversation = false
-                whenMs = whenAgo(15)
-            })
+            assertShouldNotHeadsUp(
+                buildEntry {
+                    importance = NotificationManager.IMPORTANCE_DEFAULT
+                    isConversation = true
+                    isImportantConversation = false
+                    whenMs = whenAgo(15)
+                }
+            )
         }
     }
 
@@ -113,12 +126,16 @@
     fun testAvalancheFilter_duringAvalanche_allowHighPriorityConversation() {
         avalancheProvider.startTime = whenAgo(10)
 
-        withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) {
+        withFilter(
+            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+        ) {
             ensurePeekState()
-            assertShouldHeadsUp(buildEntry {
-                importance = NotificationManager.IMPORTANCE_HIGH
-                isImportantConversation = true
-            })
+            assertShouldHeadsUp(
+                buildEntry {
+                    importance = NotificationManager.IMPORTANCE_HIGH
+                    isImportantConversation = true
+                }
+            )
         }
     }
 
@@ -126,12 +143,16 @@
     fun testAvalancheFilter_duringAvalanche_allowCall() {
         avalancheProvider.startTime = whenAgo(10)
 
-        withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) {
+        withFilter(
+            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+        ) {
             ensurePeekState()
-            assertShouldHeadsUp(buildEntry {
-                importance = NotificationManager.IMPORTANCE_HIGH
-                isCall = true
-            })
+            assertShouldHeadsUp(
+                buildEntry {
+                    importance = NotificationManager.IMPORTANCE_HIGH
+                    isCall = true
+                }
+            )
         }
     }
 
@@ -139,12 +160,16 @@
     fun testAvalancheFilter_duringAvalanche_allowCategoryReminder() {
         avalancheProvider.startTime = whenAgo(10)
 
-        withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) {
+        withFilter(
+            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+        ) {
             ensurePeekState()
-            assertShouldHeadsUp(buildEntry {
-                importance = NotificationManager.IMPORTANCE_HIGH
-                category = CATEGORY_REMINDER
-            })
+            assertShouldHeadsUp(
+                buildEntry {
+                    importance = NotificationManager.IMPORTANCE_HIGH
+                    category = CATEGORY_REMINDER
+                }
+            )
         }
     }
 
@@ -152,12 +177,16 @@
     fun testAvalancheFilter_duringAvalanche_allowCategoryEvent() {
         avalancheProvider.startTime = whenAgo(10)
 
-        withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) {
+        withFilter(
+            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+        ) {
             ensurePeekState()
-            assertShouldHeadsUp(buildEntry {
-                importance = NotificationManager.IMPORTANCE_HIGH
-                category = CATEGORY_EVENT
-            })
+            assertShouldHeadsUp(
+                buildEntry {
+                    importance = NotificationManager.IMPORTANCE_HIGH
+                    category = CATEGORY_EVENT
+                }
+            )
         }
     }
 
@@ -165,7 +194,9 @@
     fun testAvalancheFilter_duringAvalanche_allowFsi() {
         avalancheProvider.startTime = whenAgo(10)
 
-        withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) {
+        withFilter(
+            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+        ) {
             assertFsiNotSuppressed()
         }
     }
@@ -174,16 +205,44 @@
     fun testAvalancheFilter_duringAvalanche_allowColorized() {
         avalancheProvider.startTime = whenAgo(10)
 
-        withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) {
+        withFilter(
+            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+        ) {
             ensurePeekState()
-            assertShouldHeadsUp(buildEntry {
-                importance = NotificationManager.IMPORTANCE_HIGH
-                isColorized = true
-            })
+            assertShouldHeadsUp(
+                buildEntry {
+                    importance = NotificationManager.IMPORTANCE_HIGH
+                    isColorized = true
+                }
+            )
         }
     }
 
     @Test
+    fun testAvalancheFilter_duringAvalanche_allowEmergency() {
+        avalancheProvider.startTime = whenAgo(10)
+
+        `when`(
+            packageManager.checkPermission(
+                org.mockito.Mockito.eq(permission.RECEIVE_EMERGENCY_BROADCAST),
+                anyString()
+            )
+        ).thenReturn(PERMISSION_GRANTED)
+
+        withFilter(
+            AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+        ) {
+            ensurePeekState()
+            assertShouldHeadsUp(
+                buildEntry {
+                    importance = NotificationManager.IMPORTANCE_HIGH
+                }
+            )
+        }
+    }
+
+
+    @Test
     fun testPeekCondition_suppressesOnlyPeek() {
         withCondition(TestCondition(types = setOf(PEEK)) { true }) {
             assertPeekSuppressed()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index 3b979a7..71e7dc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -42,6 +42,7 @@
 import android.app.PendingIntent.FLAG_MUTABLE
 import android.content.Context
 import android.content.Intent
+import android.content.pm.PackageManager
 import android.content.pm.UserInfo
 import android.graphics.drawable.Icon
 import android.hardware.display.FakeAmbientDisplayConfiguration
@@ -129,6 +130,7 @@
     protected val userTracker = FakeUserTracker()
     protected val avalancheProvider: AvalancheProvider = mock()
     lateinit var systemSettings: SystemSettings
+    protected val packageManager: PackageManager = mock()
 
     protected abstract val provider: VisualInterruptionDecisionProvider
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
index 60aaa64..61c008b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
@@ -15,11 +15,11 @@
  */
 package com.android.systemui.statusbar.notification.interruption
 
+import android.content.pm.PackageManager
 import android.hardware.display.AmbientDisplayConfiguration
 import android.os.Handler
 import android.os.PowerManager
 import com.android.internal.logging.UiEventLogger
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.settings.UserTracker
@@ -53,7 +53,8 @@
         uiEventLogger: UiEventLogger,
         userTracker: UserTracker,
         avalancheProvider: AvalancheProvider,
-        systemSettings: SystemSettings
+        systemSettings: SystemSettings,
+        packageManager: PackageManager,
     ): VisualInterruptionDecisionProvider {
         return if (VisualInterruptionRefactor.isEnabled) {
             VisualInterruptionDecisionProviderImpl(
@@ -73,7 +74,8 @@
                 uiEventLogger,
                 userTracker,
                 avalancheProvider,
-                systemSettings
+                systemSettings,
+                packageManager
             )
         } else {
             NotificationInterruptStateProviderWrapper(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
index 2662c80..5974171 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
@@ -22,9 +22,9 @@
 import static org.mockito.Mockito.verify;
 
 import android.os.RemoteException;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.statusbar.IStatusBarService;
@@ -44,7 +44,7 @@
 import java.util.Collections;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class ExpansionStateLoggerTest extends SysuiTestCase {
     private static final String NOTIFICATION_KEY = "notin_key";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index 1113091..a8929a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -35,9 +35,9 @@
 import android.os.Looper;
 import android.os.UserHandle;
 import android.platform.test.annotations.DisableFlags;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.InstanceId;
@@ -87,7 +87,7 @@
 import java.util.concurrent.Executor;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
 public class NotificationLoggerTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
index 4b0b4b8..3ea7732 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
@@ -21,8 +21,8 @@
 import android.graphics.Bitmap
 import android.graphics.drawable.Icon
 import android.stats.sysui.NotificationEnums
-import android.testing.AndroidTestingRunner
 import android.util.StatsEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.assertLogsWtf
@@ -46,7 +46,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class NotificationMemoryLoggerTest : SysuiTestCase() {
 
     @Rule @JvmField val expect = Expect.create()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt
index 072a497..f10a52a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt
@@ -24,8 +24,8 @@
 import android.graphics.Bitmap
 import android.graphics.drawable.Icon
 import android.stats.sysui.NotificationEnums
-import android.testing.AndroidTestingRunner
 import android.widget.RemoteViews
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.NotificationUtils
@@ -36,7 +36,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class NotificationMemoryMeterTest : SysuiTestCase() {
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
index 4bb28ae..d7dde96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
@@ -3,9 +3,9 @@
 import android.app.Notification
 import android.graphics.Bitmap
 import android.graphics.drawable.Icon
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.widget.RemoteViews
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder
@@ -17,7 +17,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class NotificationMemoryViewWalkerTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt
index 9b9cb82..9f98fd4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt
@@ -17,9 +17,9 @@
 
 import android.annotation.ColorInt
 import android.graphics.Color
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.Utils
 import com.android.systemui.res.R
@@ -34,7 +34,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class ActivatableNotificationViewTest : SysuiTestCase() {
     private val mContentView: View = mock()
@@ -98,4 +98,4 @@
         assertThat(mView.topRoundness).isEqualTo(1f)
         assertThat(mView.roundableState.hashCode()).isEqualTo(roundableState.hashCode())
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
index 0eae5fc..fda5cd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
@@ -20,8 +20,8 @@
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.Icon
 import android.net.Uri
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.NotificationDrawableConsumer
 import com.android.systemui.SysuiTestCase
@@ -50,7 +50,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWithLooper
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class BigPictureIconManagerTest : SysuiTestCase() {
 
     private val testDispatcher = StandardTestDispatcher()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
index 7dcbd80..c5b19ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
@@ -25,8 +25,8 @@
 import android.graphics.Color
 import android.graphics.drawable.ColorDrawable
 import android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
 
@@ -47,7 +47,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class ChannelEditorDialogControllerTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index 210b1a7..e738b61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -21,8 +21,8 @@
 import android.net.Uri
 import android.os.UserHandle
 import android.os.UserHandle.USER_ALL
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.statusbar.IStatusBarService
@@ -71,7 +71,7 @@
 import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class ExpandableNotificationRowControllerTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
index 9d2f32d..1c5f37c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
@@ -31,10 +31,10 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
-import android.testing.AndroidTestingRunner;
 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.SysuiTestCase;
@@ -48,7 +48,7 @@
 import org.junit.runner.RunWith;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @RunWithLooper
 public class ExpandableNotificationRowDragControllerTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index aa79c23..7304bd6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -46,13 +46,13 @@
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 import android.platform.test.annotations.EnableFlags;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.DisplayMetrics;
 import android.view.View;
 import android.widget.ImageView;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
@@ -87,7 +87,7 @@
 import java.util.function.Consumer;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @RunWithLooper
 public class ExpandableNotificationRowTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
index ffb8646..d04d6fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
@@ -45,13 +45,13 @@
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
 import android.testing.UiThreadTest;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.statusbar.IStatusBarService;
@@ -72,7 +72,7 @@
 import java.util.Locale;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @UiThreadTest
 public class FeedbackInfoTest extends SysuiTestCase {
     private static final String TEST_PACKAGE_NAME = "test_package";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt
new file mode 100644
index 0000000..c325791
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.row
+
+import android.app.Flags.FLAG_COMPACT_HEADS_UP_NOTIFICATION
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HeadsUpStyleProviderImplTest : SysuiTestCase() {
+
+    @Rule @JvmField val setFlagsRule = SetFlagsRule()
+
+    private lateinit var statusBarModeRepositoryStore: FakeStatusBarModeRepository
+    private lateinit var headsUpStyleProvider: HeadsUpStyleProviderImpl
+
+    @Before
+    fun setUp() {
+        statusBarModeRepositoryStore = FakeStatusBarModeRepository()
+        statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value = true
+
+        headsUpStyleProvider = HeadsUpStyleProviderImpl(statusBarModeRepositoryStore)
+    }
+
+    @Test
+    @DisableFlags(FLAG_COMPACT_HEADS_UP_NOTIFICATION)
+    fun shouldApplyCompactStyle_returnsFalse_whenCompactFlagDisabled() {
+        assertThat(headsUpStyleProvider.shouldApplyCompactStyle()).isFalse()
+    }
+
+    @Test
+    @EnableFlags(FLAG_COMPACT_HEADS_UP_NOTIFICATION)
+    fun shouldApplyCompactStyle_returnsTrue_whenImmersiveModeEnabled() {
+        // GIVEN
+        statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value = true
+
+        // THEN
+        assertThat(headsUpStyleProvider.shouldApplyCompactStyle()).isTrue()
+    }
+
+    @Test
+    @EnableFlags(FLAG_COMPACT_HEADS_UP_NOTIFICATION)
+    fun shouldApplyCompactStyle_returnsFalse_whenImmersiveModeDisabled() {
+        // GIVEN
+        statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value = false
+
+        // THEN
+        assertThat(headsUpStyleProvider.shouldApplyCompactStyle()).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
index 25172de..18fd42d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
@@ -22,11 +22,11 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
 import androidx.annotation.NonNull;
 import androidx.core.os.CancellationSignal;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -48,7 +48,7 @@
 import java.util.List;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class NotifBindPipelineTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt
index e38adeb..29252b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt
@@ -16,8 +16,8 @@
 
 package com.android.systemui.statusbar.notification.row
 
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -33,7 +33,7 @@
 import org.mockito.Mockito.verify
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class NotifInflationErrorManagerTest : SysuiTestCase() {
     private lateinit var manager: NotifInflationErrorManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
index 20cc01a..8b1c95b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
@@ -26,9 +26,9 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
-import android.testing.AndroidTestingRunner;
 import android.widget.RemoteViews;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -45,7 +45,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class NotifRemoteViewCacheImplTest extends SysuiTestCase {
 
     private NotifRemoteViewCacheImpl mNotifRemoteViewCache;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 03a8403..a355cd1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -40,7 +40,6 @@
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.Looper;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.TypedValue;
@@ -49,6 +48,7 @@
 import android.widget.RemoteViews;
 import android.widget.TextView;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.test.filters.Suppress;
 
@@ -79,7 +79,7 @@
 import java.util.concurrent.TimeUnit;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @RunWithLooper(setAsMainLooper = true)
 @Suppress
 public class NotificationContentInflaterTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index 7332bc3..2bb610a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -20,7 +20,6 @@
 import android.content.res.Resources
 import android.os.UserHandle
 import android.service.notification.StatusBarNotification
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.ViewUtils
 import android.view.NotificationHeaderView
@@ -29,6 +28,7 @@
 import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.LinearLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.R
 import com.android.internal.widget.NotificationActionListLayout
@@ -60,7 +60,7 @@
 import org.mockito.MockitoAnnotations.initMocks
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class NotificationContentViewTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 97cb11e2..be89ab8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -65,13 +65,13 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
@@ -103,7 +103,7 @@
 import java.util.concurrent.CountDownLatch;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class NotificationConversationInfoTest extends SysuiTestCase {
     private static final String TEST_PACKAGE_NAME = "test_package";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 907649b..625963f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -57,12 +57,12 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.ArraySet;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
@@ -115,7 +115,7 @@
  * Tests for {@link NotificationGutsManager}.
  */
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class NotificationGutsManagerTest extends SysuiTestCase {
     private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
index 1b85dfa..0b5f8d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -31,11 +31,11 @@
 import android.os.userManager
 import android.provider.Settings
 import android.service.notification.NotificationListenerService.Ranking
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.util.ArraySet
 import android.view.View
 import android.view.accessibility.accessibilityManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.internal.logging.MetricsLogger
@@ -91,7 +91,7 @@
 
 /** Tests for [NotificationGutsManager] with the scene container enabled. */
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 @EnableSceneContainer
 class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
index 7f9471e..350f90d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
@@ -16,11 +16,11 @@
 
 package com.android.systemui.statusbar.notification.row
 
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.testing.ViewUtils
 import android.view.LayoutInflater
 import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
@@ -34,7 +34,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class NotificationGutsTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index 13ced92..245a6a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -55,13 +55,13 @@
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.telecom.TelecomManager;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
@@ -85,7 +85,7 @@
 import java.util.concurrent.CountDownLatch;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class NotificationInfoTest extends SysuiTestCase {
     private static final String TEST_PACKAGE_NAME = "test_package";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index e929028..027e899 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -29,13 +29,13 @@
 import static org.mockito.Mockito.when;
 
 import android.provider.Settings;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.testing.ViewUtils;
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -49,7 +49,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @RunWithLooper
 @SmallTest
 public class NotificationMenuRowTest extends LeakCheckedTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt
index 8261c1c..352b79f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt
@@ -22,8 +22,8 @@
 import android.net.Uri
 import android.os.Handler
 import android.provider.Settings.Secure
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
@@ -54,7 +54,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class NotificationSettingsControllerTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
index 4a91cd2..22f1e46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
@@ -23,11 +23,11 @@
 import static org.mockito.Mockito.mock;
 
 import android.provider.Settings;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableResources;
 import android.testing.UiThreadTest;
 import android.util.KeyValueListParser;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -41,7 +41,7 @@
 import java.util.ArrayList;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @UiThreadTest
 public class NotificationSnoozeTest extends SysuiTestCase {
     private static final int RES_DEFAULT = 2;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
index 51665d9..57b0f3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
@@ -41,7 +41,6 @@
 import android.graphics.drawable.Icon;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.text.SpannableString;
 import android.view.LayoutInflater;
@@ -49,6 +48,7 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
@@ -69,7 +69,7 @@
 import java.util.concurrent.CountDownLatch;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class PartialConversationInfoTest extends SysuiTestCase {
     private static final String TEST_PACKAGE_NAME = "test_package";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
index 1534c84..841cb4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
@@ -34,10 +34,10 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Log;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -52,7 +52,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class RowContentBindStageTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt
index 1c959af..53a1198 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt
@@ -18,8 +18,8 @@
 import android.app.Notification
 import android.app.Person
 import android.platform.test.annotations.EnableFlags
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.R
 import com.android.systemui.SysuiTestCase
@@ -34,7 +34,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class SingleLineConversationViewBinderTest : SysuiTestCase() {
     private lateinit var notificationBuilder: Notification.Builder
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 f0fc349..ee819c4 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
@@ -17,8 +17,8 @@
 
 import android.app.Notification
 import android.platform.test.annotations.EnableFlags
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.R
 import com.android.systemui.SysuiTestCase
@@ -33,7 +33,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class SingleLineViewBinderTest : SysuiTestCase() {
     private lateinit var notificationBuilder: Notification.Builder
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 b67153a..e025d3d 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
@@ -27,9 +27,9 @@
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.Icon
 import android.platform.test.annotations.EnableFlags
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import androidx.core.graphics.drawable.toBitmap
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.res.R
@@ -48,7 +48,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 @EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
 class SingleLineViewInflaterTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt
index d46763d..f8533a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt
@@ -16,11 +16,11 @@
 
 package com.android.systemui.statusbar.notification.row
 
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.text.PrecomputedText
 import android.text.TextPaint
 import android.widget.TextView
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
@@ -29,7 +29,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class TextPrecomputerTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt
index c960230..0dc871a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt
@@ -18,7 +18,7 @@
 
 package com.android.systemui.statusbar.notification.row.ui.viewmodel
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.accessibility.data.repository.FakeAccessibilityRepository
@@ -31,7 +31,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class ActivatableNotificationViewModelTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java
index a15b4cd..ec280a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java
@@ -24,10 +24,10 @@
 import android.graphics.drawable.AnimatedImageDrawable;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
-import android.testing.AndroidTestingRunner;
 import android.view.LayoutInflater;
 import android.view.View;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
@@ -40,7 +40,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @SmallTest
 public class NotificationBigPictureTemplateViewWrapperTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
index fe2971c..9d990b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.notification.row.wrapper
 
 import android.graphics.drawable.AnimatedImageDrawable
-import android.testing.AndroidTestingRunner
 import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.R
 import com.android.internal.widget.CachingIconView
@@ -38,7 +38,7 @@
 import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class NotificationConversationTemplateViewWrapperTest : SysuiTestCase() {
 
     private lateinit var mRow: ExpandableNotificationRow
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
index 2d72c7e..f9a9704 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
@@ -16,10 +16,10 @@
 
 package com.android.systemui.statusbar.notification.row.wrapper;
 
-import android.testing.AndroidTestingRunner;
 import android.view.View;
 import android.widget.RemoteViews;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -33,7 +33,7 @@
 import org.junit.runner.RunWith;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class NotificationCustomViewWrapperTest extends SysuiTestCase {
 
     private ExpandableNotificationRow mRow;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt
index f26c18b..fc829d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.notification.row.wrapper
 
 import android.graphics.drawable.AnimatedImageDrawable
-import android.testing.AndroidTestingRunner
 import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.MessagingGroup
 import com.android.internal.widget.MessagingImageMessage
@@ -36,7 +36,7 @@
 import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class NotificationMessagingTemplateViewWrapperTest : SysuiTestCase() {
 
     private lateinit var mRow: ExpandableNotificationRow
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt
index 54eed26..1ce3bad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt
@@ -19,7 +19,6 @@
 import android.app.PendingIntent
 import android.app.PendingIntent.CancelListener
 import android.content.Intent
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
 import android.testing.ViewUtils
@@ -27,6 +26,7 @@
 import android.view.View
 import android.view.ViewGroup
 import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.R
 import com.android.systemui.SysuiTestCase
@@ -46,7 +46,7 @@
 import org.mockito.Mockito.verify
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class NotificationTemplateViewWrapperTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
index fad85f53..d17c8db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
@@ -20,11 +20,11 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.testing.AndroidTestingRunner;
 import android.view.View;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -35,7 +35,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @SmallTest
 public class NotificationViewWrapperTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
index 59d98c2..4c6e25a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
@@ -19,7 +19,7 @@
 package com.android.systemui.statusbar.notification.shelf.domain.interactor
 
 import android.os.PowerManager
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
@@ -41,7 +41,7 @@
 import org.mockito.Mockito.isNull
 import org.mockito.Mockito.verify
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class NotificationShelfInteractorTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
index 917569c..e2fb3ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.notification.shelf.ui.viewmodel
 
 import android.os.PowerManager
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysUITestComponent
 import com.android.systemui.SysUITestModule
@@ -44,7 +44,7 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.verify
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class NotificationShelfViewModelTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
index fb15948..2349c25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.notification.stack
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
@@ -33,7 +33,7 @@
 
 private const val MAX_PULSE_HEIGHT = 100000f
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class AmbientStateTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt
index f4e236e..3a77d82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt
@@ -1,8 +1,8 @@
 package com.android.systemui.statusbar.notification.stack
 
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.LayoutInflater
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.res.R
@@ -15,7 +15,7 @@
  * Tests for {@link MediaContainView}.
  */
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class MediaContainerViewTest : SysuiTestCase() {
 
@@ -35,4 +35,4 @@
         mediaContainerView.updateClipping()
         assertTrue(mediaContainerView.clipHeight == 10)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index 3b16f14..14bbd38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -21,13 +21,13 @@
 import android.app.Notification;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.NotificationHeaderView;
 import android.view.View;
 import android.widget.RemoteViews;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -45,7 +45,7 @@
 import java.util.List;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @RunWithLooper
 //@DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME)
 public class NotificationChildrenContainerTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 745d20d..48e8f88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -1,10 +1,11 @@
 package com.android.systemui.statusbar.notification.stack
 
+import android.platform.test.annotations.DisableFlags
 import android.service.notification.StatusBarNotification
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.LayoutInflater
 import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
 import com.android.systemui.SysuiTestCase
@@ -34,7 +35,7 @@
 
 /** Tests for {@link NotificationShelf}. */
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 open class NotificationShelfTest : SysuiTestCase() {
 
@@ -69,8 +70,8 @@
     }
 
     @Test
+    @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME)
     fun testShadeWidth_BasedOnFractionToShade() {
-        mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
         setFractionToShade(0f)
         setOnLockscreen(true)
 
@@ -85,8 +86,8 @@
     }
 
     @Test
+    @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME)
     fun testShelfIsLong_WhenNotOnLockscreen() {
-        mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
         setFractionToShade(0f)
         setOnLockscreen(false)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index a66a136..ce2491b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -24,6 +24,7 @@
 
 import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
 
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -41,21 +42,25 @@
 import android.metrics.LogMaker;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewTreeObserver;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto;
+import com.android.systemui.ExpandHelper;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.DisableSceneContainer;
+import com.android.systemui.flags.EnableSceneContainer;
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -121,7 +126,7 @@
  */
 @SmallTest
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
 
     protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
@@ -171,6 +176,7 @@
     @Mock private NotificationListViewBinder mViewBinder;
     @Mock
     private SensitiveNotificationProtectionController mSensitiveNotificationProtectionController;
+    @Mock private ExpandHelper mExpandHelper;
 
     @Captor
     private ArgumentCaptor<Runnable> mSensitiveStateListenerArgumentCaptor;
@@ -895,6 +901,50 @@
         verify(mSensitiveNotificationProtectionController).registerSensitiveStateListener(any());
     }
 
+    @Test
+    @EnableSceneContainer
+    public void onTouchEvent_stopExpandingNotification_sceneContainerEnabled() {
+        boolean touchHandled = stopExpandingNotification();
+
+        verify(mNotificationStackScrollLayout).startOverscrollAfterExpanding();
+        verify(mNotificationStackScrollLayout, never()).dispatchDownEventToScroller(any());
+        assertTrue(touchHandled);
+    }
+
+    @Test
+    @DisableSceneContainer
+    public void onTouchEvent_stopExpandingNotification_sceneContainerDisabled() {
+        stopExpandingNotification();
+
+        verify(mNotificationStackScrollLayout, never()).startOverscrollAfterExpanding();
+        verify(mNotificationStackScrollLayout).dispatchDownEventToScroller(any());
+    }
+
+    private boolean stopExpandingNotification() {
+        when(mNotificationStackScrollLayout.getExpandHelper()).thenReturn(mExpandHelper);
+        when(mNotificationStackScrollLayout.getIsExpanded()).thenReturn(true);
+        when(mNotificationStackScrollLayout.getExpandedInThisMotion()).thenReturn(true);
+        when(mNotificationStackScrollLayout.isExpandingNotification()).thenReturn(true);
+
+        when(mExpandHelper.onTouchEvent(any())).thenAnswer(i -> {
+            when(mNotificationStackScrollLayout.isExpandingNotification()).thenReturn(false);
+            return false;
+        });
+
+        initController(/* viewIsAttached= */ true);
+        NotificationStackScrollLayoutController.TouchHandler touchHandler =
+                mController.getTouchHandler();
+
+        return touchHandler.onTouchEvent(MotionEvent.obtain(
+                /* downTime= */ 0,
+                /* eventTime= */ 0,
+                MotionEvent.ACTION_DOWN,
+                0,
+                0,
+                /* metaState= */ 0
+        ));
+    }
+
     private LogMaker logMatcher(int category, int type) {
         return argThat(new LogMatcher(category, type));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 939d055..f461e2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -54,7 +54,6 @@
 import android.os.SystemClock;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableResources;
 import android.util.MathUtils;
@@ -65,6 +64,7 @@
 import android.view.WindowInsetsAnimation;
 import android.widget.TextView;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.BouncerPanelExpansionCalculator;
@@ -114,7 +114,7 @@
  * Tests for {@link NotificationStackScrollLayout}.
  */
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class NotificationStackScrollLayoutTest extends SysuiTestCase {
 
@@ -207,6 +207,7 @@
                 .thenReturn(mNotificationRoundnessManager);
         mStackScroller.setController(mStackScrollLayoutController);
         mStackScroller.setShelf(mNotificationShelf);
+        when(mStackScroller.getExpandHelper()).thenReturn(mExpandHelper);
 
         doNothing().when(mGroupExpansionManager).collapseGroups();
         doNothing().when(mExpandHelper).cancelImmediately();
@@ -1139,6 +1140,14 @@
         assertFalse(mStackScroller.mHeadsUpAnimatingAway);
     }
 
+    @Test
+    @EnableSceneContainer
+    public void finishExpanding_sceneContainerEnabled() {
+        mStackScroller.startOverscrollAfterExpanding();
+        verify(mStackScroller.getExpandHelper()).finishExpanding();
+        assertTrue(mStackScroller.getIsBeingDragged());
+    }
+
     private MotionEvent captureTouchSentToSceneFramework() {
         ArgumentCaptor<MotionEvent> captor = ArgumentCaptor.forClass(MotionEvent.class);
         verify(mStackScrollLayoutController).sendTouchToSceneFramework(captor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
index 6fec9ad..dae5542 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
@@ -18,8 +18,8 @@
 
 import android.annotation.DimenRes
 import android.service.notification.StatusBarNotification
-import android.testing.AndroidTestingRunner
 import android.view.View.VISIBLE
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
@@ -44,7 +44,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class NotificationStackSizeCalculatorTest : SysuiTestCase() {
 
     @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 85a2bdd..2d11917 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -37,12 +37,12 @@
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.os.Handler;
 import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -70,7 +70,7 @@
  * Tests for {@link NotificationSwipeHelper}.
  */
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper()
 public class NotificationSwipeHelperTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
index e30947c..660eb30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
@@ -1,8 +1,8 @@
 package com.android.systemui.statusbar.notification.stack
 
-import android.testing.AndroidTestingRunner
 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.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlags
@@ -15,7 +15,7 @@
 
 /** Tests for {@link NotificationTargetsHelper}. */
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class NotificationTargetsHelperTest : SysuiTestCase() {
     private val featureFlags = FakeFeatureFlags()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 82725d6..a6fb718 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -394,8 +394,20 @@
     }
 
     @Test
+    fun resetViewStates_shadeCollapsed_emptyShadeViewBecomesTransparent() {
+        ambientState.expansionFraction = 0f
+        stackScrollAlgorithm.initView(context)
+        hostView.removeAllViews()
+        hostView.addView(emptyShadeView)
+
+        stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
+
+        assertThat(emptyShadeView.viewState.alpha).isEqualTo(0f)
+    }
+
+    @Test
     fun resetViewStates_isOnKeyguard_emptyShadeViewBecomesOpaque() {
-        ambientState.setStatusBarState(StatusBarState.SHADE)
+        ambientState.setStatusBarState(StatusBarState.KEYGUARD)
         ambientState.fractionToShade = 0.25f
         stackScrollAlgorithm.initView(context)
         hostView.removeAllViews()
@@ -403,7 +415,8 @@
 
         stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
 
-        assertThat(emptyShadeView.viewState.alpha).isEqualTo(1f)
+        val expected = getContentAlpha(ambientState.fractionToShade)
+        assertThat(emptyShadeView.viewState.alpha).isEqualTo(expected)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
index 4f0f91a..798465e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.notification.stack
 
 import android.platform.test.annotations.EnableFlags
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.AnimatorTestRule
@@ -48,7 +48,7 @@
 private const val HEADS_UP_ABOVE_SCREEN = 80
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class StackStateAnimatorTest : SysuiTestCase() {
 
@@ -134,7 +134,8 @@
                 /* isHeadsUpAnimation= */ eq(true),
                 /* onStartedRunnable= */ any(),
                 /* onFinishedRunnable= */ runnableCaptor.capture(),
-                /* animationListener= */ any()
+                /* animationListener= */ any(),
+                /* clipSide= */ eq(ExpandableView.ClipSide.BOTTOM),
             )
 
         animatorTestRule.advanceTimeBy(disappearDuration) // move to the end of SSA animations
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
index cd6bb5f..e493420 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.notification.stack
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.assertDoesNotLogWtf
@@ -27,7 +27,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class ViewStateTest : SysuiTestCase() {
     private val viewState = ViewState()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
index e2ac203..e46906f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
@@ -17,10 +17,10 @@
 
 import android.content.res.Configuration
 import android.graphics.Rect
-import android.testing.AndroidTestingRunner
 import android.view.Surface
 import android.view.Surface.ROTATION_0
 import android.view.Surface.ROTATION_90
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
@@ -52,7 +52,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 open class HideNotificationsInteractorTest : SysuiTestCase() {
 
     private val testScope = TestScope()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 84cd518..f0bc655 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -45,10 +45,10 @@
 import android.hardware.display.NightDisplayListener;
 import android.os.Handler;
 import android.os.UserHandle;
-import android.testing.AndroidTestingRunner;
 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.SysuiTestCase;
@@ -89,7 +89,7 @@
 
 import javax.inject.Named;
 
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @RunWithLooper
 @SmallTest
 public class AutoTileManagerTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index dc7525c..285949a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -37,11 +37,11 @@
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.UserHandle;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.testing.TestableResources;
 import android.view.ViewRootImpl;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
@@ -77,7 +77,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @RunWithLooper
 public class BiometricsUnlockControllerTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index c088609..5675915 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -30,9 +30,9 @@
 import android.os.PowerManager;
 import android.os.UserHandle;
 import android.os.Vibrator;
-import android.testing.AndroidTestingRunner;
 import android.view.HapticFeedbackConstants;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
@@ -51,6 +51,7 @@
 import com.android.systemui.shade.ShadeHeaderController;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -70,7 +71,7 @@
 import java.util.Optional;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
 
     @Mock private CentralSurfaces mCentralSurfaces;
@@ -80,6 +81,7 @@
     @Mock private QuickSettingsController mQuickSettingsController;
     @Mock private ShadeViewController mShadeViewController;
     @Mock private PanelExpansionInteractor mPanelExpansionInteractor;
+    @Mock private Lazy<ShadeInteractor> mShadeInteractorLazy;
     @Mock private ShadeHeaderController mShadeHeaderController;
     @Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
     private final MetricsLogger mMetricsLogger = new FakeMetricsLogger();
@@ -115,6 +117,7 @@
                 mShadeController,
                 mCommandQueue,
                 mPanelExpansionInteractor,
+                mShadeInteractorLazy,
                 mShadeHeaderController,
                 mRemoteInputQuickSettingsDisabler,
                 mMetricsLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index b9312d3..cde241b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -21,6 +21,7 @@
 import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED;
 import static android.provider.Settings.Global.HEADS_UP_ON;
 
+import static com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR;
 import static com.android.systemui.Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE;
 import static com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION;
 import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT;
@@ -56,6 +57,7 @@
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.hardware.devicestate.DeviceState;
@@ -70,9 +72,10 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.service.dreams.IDreamManager;
 import android.support.test.metricshelper.MetricsAsserts;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.DisplayMetrics;
@@ -82,6 +85,7 @@
 import android.view.WindowManager;
 import android.view.WindowMetrics;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.compose.animation.scene.ObservableTransitionState;
@@ -105,8 +109,6 @@
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.communal.data.repository.CommunalRepository;
-import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.communal.shared.model.CommunalScenes;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
@@ -222,8 +224,9 @@
 import javax.inject.Provider;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @RunWithLooper(setAsMainLooper = true)
+@EnableFlags(FLAG_LIGHT_REVEAL_MIGRATION)
 public class CentralSurfacesImplTest extends SysuiTestCase {
 
     private static final int FOLD_STATE_FOLDED = 0;
@@ -238,8 +241,6 @@
 
 
     private final TestScope mTestScope = mKosmos.getTestScope();
-    private final CommunalInteractor mCommunalInteractor = mKosmos.getCommunalInteractor();
-    private final CommunalRepository mCommunalRepository = mKosmos.getCommunalRepository();
     @Mock private NotificationsController mNotificationsController;
     @Mock private LightBarController mLightBarController;
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -339,6 +340,7 @@
     @Mock private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
     @Mock private KeyboardShortcuts mKeyboardShortcuts;
     @Mock private KeyboardShortcutListSearch mKeyboardShortcutListSearch;
+    @Mock private PackageManager mPackageManager;
 
     private ShadeController mShadeController;
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
@@ -362,13 +364,9 @@
 
         // Set default value to avoid IllegalStateException.
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
-        mSetFlagsRule.enableFlags(FLAG_LIGHT_REVEAL_MIGRATION);
         // Turn AOD on and toggle feature flag for jank fixes
         mFeatureFlags.set(Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD, true);
         when(mDozeParameters.getAlwaysOn()).thenReturn(true);
-        if (!SceneContainerFlag.isEnabled()) {
-            mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
-        }
 
         IThermalService thermalService = mock(IThermalService.class);
         mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService,
@@ -396,7 +394,8 @@
                         mock(UiEventLogger.class),
                         mUserTracker,
                         mAvalancheProvider,
-                        mSystemSettings);
+                        mSystemSettings,
+                        mPackageManager);
         mVisualInterruptionDecisionProvider.start();
 
         mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
@@ -528,7 +527,7 @@
                 mScreenLifecycle,
                 mWakefulnessLifecycle,
                 mPowerInteractor,
-                mCommunalInteractor,
+                mKosmos.getCommunalInteractor(),
                 mStatusBarStateController,
                 Optional.of(mBubbles),
                 () -> mNoteTaskController,
@@ -837,6 +836,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
     public void testSetDozingNotUnlocking_transitionToAuthScrimmed_cancelKeyguardFadingAway() {
         when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
         when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true);
@@ -848,7 +848,8 @@
     }
 
     @Test
-    public void testOccludingQSNotExpanded_transitionToAuthScrimmed() {
+    @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+    public void testOccludingQSNotExpanded_flagOff_transitionToAuthScrimmed() {
         when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
 
         // GIVEN device occluded and panel is NOT expanded
@@ -862,6 +863,39 @@
     }
 
     @Test
+    @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+    public void testNotOccluding_QSNotExpanded_flagOn_doesNotTransitionScrimState() {
+        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+
+        // GIVEN device occluded and panel is NOT expanded
+        mCentralSurfaces.setBarStateForTest(SHADE);
+        when(mKeyguardStateController.isOccluded()).thenReturn(false);
+        when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(false);
+
+        mCentralSurfaces.updateScrimController();
+
+        // Tests the safeguard to reset the scrimstate
+        verify(mScrimController, never()).transitionTo(any());
+    }
+
+    @Test
+    @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+    public void testNotOccluding_QSExpanded_flagOn_doesTransitionScrimStateToKeyguard() {
+        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+
+        // GIVEN device occluded and panel is NOT expanded
+        mCentralSurfaces.setBarStateForTest(SHADE);
+        when(mKeyguardStateController.isOccluded()).thenReturn(false);
+        when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(true);
+
+        mCentralSurfaces.updateScrimController();
+
+        // Tests the safeguard to reset the scrimstate
+        verify(mScrimController, never()).transitionTo(eq(ScrimState.KEYGUARD));
+    }
+
+    @Test
+    @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
     public void testOccludingQSExpanded_transitionToAuthScrimmedShade() {
         when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
 
@@ -878,16 +912,18 @@
     @Test
     public void testEnteringGlanceableHub_updatesScrim() {
         // Transition to the glanceable hub.
-        mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
-                CommunalScenes.Communal)));
+        mKosmos.getCommunalRepository()
+                .setTransitionState(
+                        flowOf(new ObservableTransitionState.Idle(CommunalScenes.Communal)));
         mTestScope.getTestScheduler().runCurrent();
 
         // ScrimState also transitions.
         verify(mScrimController).transitionTo(ScrimState.GLANCEABLE_HUB);
 
         // Transition away from the glanceable hub.
-        mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
-                CommunalScenes.Blank)));
+        mKosmos.getCommunalRepository()
+                .setTransitionState(
+                        flowOf(new ObservableTransitionState.Idle(CommunalScenes.Blank)));
         mTestScope.getTestScheduler().runCurrent();
 
         // ScrimState goes back to UNLOCKED.
@@ -901,16 +937,18 @@
         when(mKeyguardUpdateMonitor.isDreaming()).thenReturn(true);
 
         // Transition to the glanceable hub.
-        mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
-                CommunalScenes.Communal)));
+        mKosmos.getCommunalRepository()
+                .setTransitionState(
+                        flowOf(new ObservableTransitionState.Idle(CommunalScenes.Communal)));
         mTestScope.getTestScheduler().runCurrent();
 
         // ScrimState also transitions.
         verify(mScrimController).transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
 
         // Transition away from the glanceable hub.
-        mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
-                CommunalScenes.Blank)));
+        mKosmos.getCommunalRepository()
+                .setTransitionState(
+                        flowOf(new ObservableTransitionState.Idle(CommunalScenes.Blank)));
         mTestScope.getTestScheduler().runCurrent();
 
         // ScrimState goes back to UNLOCKED.
@@ -1105,18 +1143,16 @@
     }
 
     @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
     public void updateResources_flagEnabled_doesNotUpdateStatusBarWindowHeight() {
-        mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX);
-
         mCentralSurfaces.updateResources();
 
         verify(mStatusBarWindowController, never()).refreshStatusBarHeight();
     }
 
     @Test
+    @DisableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
     public void updateResources_flagDisabled_updatesStatusBarWindowHeight() {
-        mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX);
-
         mCentralSurfaces.updateResources();
 
         verify(mStatusBarWindowController).refreshStatusBarHeight();
@@ -1151,10 +1187,10 @@
     }
 
     @Test
+    @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void dismissKeyboardShortcuts_largeScreen_bothFlagsEnabled_doesNotDismissAny() {
         switchToLargeScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
-        mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         dismissKeyboardShortcuts();
@@ -1163,10 +1199,10 @@
     }
 
     @Test
+    @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void dismissKeyboardShortcuts_largeScreen_newFlagsDisabled_dismissesTabletVersion() {
         switchToLargeScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
-        mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         dismissKeyboardShortcuts();
@@ -1175,10 +1211,10 @@
     }
 
     @Test
+    @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void dismissKeyboardShortcuts_largeScreen_bothFlagsDisabled_dismissesPhoneVersion() {
         switchToLargeScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
-        mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         dismissKeyboardShortcuts();
@@ -1188,10 +1224,10 @@
     }
 
     @Test
+    @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void dismissKeyboardShortcuts_smallScreen_bothFlagsEnabled_doesNotDismissAny() {
         switchToSmallScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
-        mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         dismissKeyboardShortcuts();
@@ -1200,10 +1236,10 @@
     }
 
     @Test
+    @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void dismissKeyboardShortcuts_smallScreen_newFlagsDisabled_dismissesPhoneVersion() {
         switchToSmallScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
-        mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         dismissKeyboardShortcuts();
@@ -1213,10 +1249,10 @@
     }
 
     @Test
+    @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void dismissKeyboardShortcuts_smallScreen_bothFlagsDisabled_dismissesPhoneVersion() {
         switchToSmallScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
-        mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         dismissKeyboardShortcuts();
@@ -1226,10 +1262,10 @@
     }
 
     @Test
+    @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void toggleKeyboardShortcuts_largeScreen_bothFlagsEnabled_doesNotTogglesAny() {
         switchToLargeScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
-        mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         int deviceId = 321;
@@ -1239,10 +1275,10 @@
     }
 
     @Test
+    @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void toggleKeyboardShortcuts_largeScreen_newFlagsDisabled_togglesTabletVersion() {
         switchToLargeScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
-        mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         int deviceId = 654;
@@ -1253,10 +1289,10 @@
     }
 
     @Test
+    @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void toggleKeyboardShortcuts_largeScreen_bothFlagsDisabled_togglesPhoneVersion() {
         switchToLargeScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
-        mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         int deviceId = 987;
@@ -1267,10 +1303,10 @@
     }
 
     @Test
+    @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void toggleKeyboardShortcuts_smallScreen_bothFlagsEnabled_doesNotToggleAny() {
         switchToSmallScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
-        mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         int deviceId = 789;
@@ -1280,10 +1316,10 @@
     }
 
     @Test
+    @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void toggleKeyboardShortcuts_smallScreen_newFlagsDisabled_togglesPhoneVersion() {
         switchToSmallScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
-        mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         int deviceId = 456;
@@ -1294,10 +1330,10 @@
     }
 
     @Test
+    @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
     public void toggleKeyboardShortcuts_smallScreen_bothFlagsDisabled_togglesPhoneVersion() {
         switchToSmallScreen();
         mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
-        mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
         createCentralSurfaces();
 
         int deviceId = 123;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
index 56d2397..942ea65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
@@ -21,7 +21,7 @@
 import android.content.res.Configuration.UI_MODE_NIGHT_YES
 import android.content.res.Configuration.UI_MODE_TYPE_CAR
 import android.os.LocaleList
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
@@ -36,7 +36,7 @@
 import org.mockito.Mockito.verify
 import java.util.Locale
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class ConfigurationControllerImplTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java
index 34c43ef..3b3ec26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java
@@ -20,9 +20,9 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -36,7 +36,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 @SmallTest
 public class DozeScrimControllerTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt
index 5d42d51..a3e2d19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt
@@ -16,7 +16,7 @@
 package com.android.systemui.statusbar.phone
 
 import android.hardware.devicestate.DeviceState
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.R
 import com.android.systemui.SysuiTestCase
@@ -30,7 +30,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations.initMocks
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class FoldStateListenerTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 3e9006e..0d06b64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -26,12 +26,12 @@
 import static org.mockito.Mockito.when;
 
 import android.platform.test.annotations.DisableFlags;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 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;
@@ -62,7 +62,7 @@
 import java.util.Optional;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @RunWithLooper
 public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
index 9b4f931..cb40f72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -66,7 +66,7 @@
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
 @TestableLooper.RunWithLooper
-class KeyguardBypassControllerTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class KeyguardBypassControllerTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val featureFlags = FakeFeatureFlags()
@@ -92,7 +92,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Captor
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 fd295b5..cf87afb 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
@@ -26,8 +26,10 @@
 import static org.mockito.Mockito.when;
 
 import android.content.res.Resources;
-import android.testing.AndroidTestingRunner;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.Flags;
@@ -48,7 +50,7 @@
 import org.mockito.quality.Strictness;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
     private static final int SCREEN_HEIGHT = 2000;
     private static final int EMPTY_HEIGHT = 0;
@@ -297,8 +299,8 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
     public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOff_usesResource() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX);
         int keyguardSplitShadeTopMargin = 100;
         int largeScreenHeaderHeightResource = 70;
         when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin))
@@ -316,8 +318,8 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
     public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOn_usesHelper() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX);
         int keyguardSplitShadeTopMargin = 100;
         int largeScreenHeaderHeightHelper = 50;
         int largeScreenHeaderHeightResource = 70;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java
index b0aa2d3..d880bec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java
@@ -20,8 +20,8 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.testing.AndroidTestingRunner;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -35,7 +35,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @SmallTest
 public class KeyguardDismissUtilTest extends SysuiTestCase {
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextViewTest.java
index 5cea931..109cd94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextViewTest.java
@@ -21,10 +21,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -35,7 +35,7 @@
 import org.junit.runner.RunWith;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class KeyguardIndicationTextViewTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index dfee737..71f09a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -42,11 +42,11 @@
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.LayoutInflater;
 import android.view.View;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.CarrierTextController;
@@ -71,7 +71,6 @@
 import com.android.systemui.shade.ShadeViewStateProvider;
 import com.android.systemui.shade.data.repository.FakeShadeRepository;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository;
 import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor;
@@ -100,7 +99,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
     @Mock
@@ -144,8 +143,6 @@
     @Mock private SecureSettings mSecureSettings;
     @Mock private CommandQueue mCommandQueue;
     @Mock private KeyguardLogger mLogger;
-
-    @Mock private NotificationMediaManager mNotificationMediaManager;
     @Mock private StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory;
 
     private TestShadeViewStateProvider mShadeViewStateProvider;
@@ -225,7 +222,6 @@
                 mFakeExecutor,
                 mBackgroundExecutor,
                 mLogger,
-                mNotificationMediaManager,
                 mStatusOverlayHoverListenerFactory
         );
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java
index c44f979..0932a0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java
@@ -18,11 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.LayoutInflater;
 import android.view.View;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -33,7 +33,7 @@
 import org.junit.runner.RunWith;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class KeyguardStatusBarViewTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java
index f91064b..782ca91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java
@@ -26,7 +26,6 @@
 import static org.mockito.Mockito.when;
 
 import android.platform.test.annotations.DisableFlags;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.Display;
 import android.view.View;
@@ -35,6 +34,7 @@
 import android.view.WindowManager;
 
 import androidx.lifecycle.Observer;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -54,7 +54,7 @@
 import java.util.Objects;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @RunWithLooper
 @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
 public class LegacyLightsOutNotifControllerTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
index 9d53b9c..fea0e72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
@@ -21,9 +21,9 @@
 import static org.mockito.Mockito.when;
 
 import android.platform.test.annotations.DisableFlags;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.Flags;
@@ -49,7 +49,7 @@
 import java.util.Optional;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME)
 public class LegacyNotificationIconAreaControllerImplTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
index e7b287c..518b327 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
@@ -18,9 +18,9 @@
 
 import android.graphics.Color
 import android.graphics.Rect
-import android.testing.AndroidTestingRunner
 import android.view.WindowInsetsController
 import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.statusbar.LetterboxDetails
 import com.android.internal.view.AppearanceRegion
@@ -36,7 +36,7 @@
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class LetterboxAppearanceCalculatorTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
index 1cc0bd3..788c2cb2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
@@ -21,8 +21,8 @@
 import android.graphics.Color
 import android.os.Handler
 import android.os.Looper
-import android.testing.AndroidTestingRunner
 import android.view.IWindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -40,7 +40,7 @@
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class LetterboxBackgroundProviderTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index 7271a5e..a27073c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -35,10 +35,10 @@
 
 import android.graphics.Color;
 import android.graphics.Rect;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
 import androidx.annotation.ColorInt;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
@@ -67,7 +67,7 @@
 import java.util.List;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class LightBarControllerTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
index f71114d..43c19b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
@@ -28,9 +28,9 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.policy.GestureNavigationSettingsObserver;
@@ -48,7 +48,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class LightBarTransitionsControllerTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index 9f4e1dd..9d97e5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -16,8 +16,8 @@
 package com.android.systemui.statusbar.phone
 
 import android.service.notification.StatusBarNotification
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.StatusBarIconView
@@ -34,7 +34,7 @@
 
 /** Tests for {@link NotificationIconContainer}. */
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class NotificationIconContainerTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java
index ccd1a8c..9522e1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java
@@ -22,11 +22,11 @@
 import static org.mockito.Mockito.when;
 
 import android.content.res.Resources;
-import android.testing.AndroidTestingRunner;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -42,7 +42,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class NotificationTapHelperTest extends SysuiTestCase {
 
     private NotificationTapHelper mNotificationTapHelper;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index 8d2c158..f2f336c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -22,9 +22,9 @@
 import android.content.SharedPreferences
 import android.os.UserManager
 import android.telecom.TelecomManager
-import android.testing.AndroidTestingRunner
 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.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
@@ -79,7 +79,7 @@
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 1000329..416a869 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -49,12 +49,12 @@
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.Color;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.ViewUtils;
 import android.util.MathUtils;
 import android.view.View;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
@@ -107,7 +107,7 @@
 import java.util.HashSet;
 import java.util.Map;
 
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 public class ScrimControllerTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt
index 61da701..b9cfe21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.statusbar.phone
 
 import android.graphics.Rect
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.View
 import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.phone.StatusBarBoundsProvider.BoundsChangeListener
@@ -37,7 +37,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper(setAsMainLooper = true)
 @SmallTest
 class StatusBarBoundsProviderTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 6b3c005..3ca4c59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -41,7 +41,6 @@
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.service.trust.TrustAgentService;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.MotionEvent;
 import android.view.View;
@@ -55,6 +54,7 @@
 import android.window.OnBackInvokedDispatcher;
 import android.window.WindowOnBackInvokedDispatcher;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.LatencyTracker;
@@ -119,7 +119,7 @@
 import java.util.Optional;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 127a3d7..9fa392f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -51,9 +51,9 @@
 import android.os.UserHandle;
 import android.service.dreams.IDreamManager;
 import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.jank.InteractionJankMonitor;
@@ -119,7 +119,7 @@
 import java.util.Optional;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
 
@@ -168,6 +168,8 @@
     private FakePowerRepository mPowerRepository;
     @Mock
     private UserTracker mUserTracker;
+    @Mock
+    private HeadsUpManager mHeadsUpManager;
     private final FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
     private ExpandableNotificationRow mNotificationRow;
     private ExpandableNotificationRow mBubbleNotificationRow;
@@ -222,13 +224,12 @@
                 mScreenOffAnimationController,
                 mStatusBarStateController).getPowerInteractor();
 
-        HeadsUpManager headsUpManager = mock(HeadsUpManager.class);
         NotificationLaunchAnimatorControllerProvider notificationAnimationProvider =
                 new NotificationLaunchAnimatorControllerProvider(
                         new NotificationLaunchAnimationInteractor(
                                 new NotificationLaunchAnimationRepository()),
                         mock(NotificationListContainer.class),
-                        headsUpManager,
+                        mHeadsUpManager,
                         mJankMonitor);
         mNotificationActivityStarter =
                 new StatusBarNotificationActivityStarter(
@@ -237,7 +238,7 @@
                         mHandler,
                         mUiBgExecutor,
                         mVisibilityProvider,
-                        headsUpManager,
+                        mHeadsUpManager,
                         mActivityStarter,
                         mCommandQueue,
                         mClickNotifier,
@@ -417,6 +418,51 @@
     }
 
     @Test
+    public void testOnNotificationBubbleIconClicked_unbubble_keyGuardShowing()
+            throws RemoteException {
+        NotificationEntry entry = mBubbleNotificationRow.getEntry();
+        StatusBarNotification sbn = entry.getSbn();
+
+        // Given
+        sbn.getNotification().contentIntent = mContentIntent;
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
+
+        // When
+        mNotificationActivityStarter.onNotificationBubbleIconClicked(entry);
+
+        // Then
+        verify(mBubblesManager).onUserChangedBubble(entry, false);
+
+        verify(mHeadsUpManager).removeNotification(entry.getKey(), true);
+
+        verifyNoMoreInteractions(mContentIntent);
+        verifyNoMoreInteractions(mShadeController);
+    }
+
+    @Test
+    public void testOnNotificationBubbleIconClicked_bubble_keyGuardShowing() {
+        NotificationEntry entry = mNotificationRow.getEntry();
+        StatusBarNotification sbn = entry.getSbn();
+
+        // Given
+        sbn.getNotification().contentIntent = mContentIntent;
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
+
+        // When
+        mNotificationActivityStarter.onNotificationBubbleIconClicked(entry);
+
+        // Then
+        verify(mBubblesManager).onUserChangedBubble(entry, true);
+
+        verify(mHeadsUpManager).removeNotification(entry.getKey(), true);
+
+        verify(mContentIntent, atLeastOnce()).isActivity();
+        verifyNoMoreInteractions(mContentIntent);
+    }
+
+    @Test
     public void testOnFullScreenIntentWhenDozing_wakeUpDevice() {
         // GIVEN entry that can has a full screen intent that can show
         PendingIntent fullScreenIntent = PendingIntent.getActivity(mContext, 1,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index a8c5fc3..95472cad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -34,10 +34,10 @@
 import android.app.StatusBarManager;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
-import android.testing.AndroidTestingRunner;
 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;
@@ -85,7 +85,7 @@
 import java.util.Set;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @RunWithLooper()
 public class StatusBarNotificationPresenterTest extends SysuiTestCase {
     private StatusBarNotificationPresenter mStatusBarNotificationPresenter;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index 929099a..35888a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -24,10 +24,10 @@
 import static org.mockito.internal.verification.VerificationModeFactory.times;
 
 import android.content.Intent;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -52,7 +52,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
@@ -103,4 +103,4 @@
 
         verify(mStatusBarKeyguardViewManager).showBouncer(true);
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt
index 1455693..11dd587 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt
@@ -21,7 +21,6 @@
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.PaintDrawable
 import android.os.SystemClock
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
 import android.testing.ViewUtils
@@ -30,6 +29,7 @@
 import android.view.ViewGroupOverlay
 import android.widget.LinearLayout
 import androidx.annotation.ColorInt
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
@@ -45,7 +45,7 @@
 import org.junit.runner.RunWith
 import org.mockito.Mockito.verify
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper(setAsMainLooper = true)
 @SmallTest
 class StatusOverlayHoverListenerTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
index dedd0af..b560c59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
@@ -15,9 +15,9 @@
 
 import android.app.Dialog
 import android.content.res.Configuration
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.WindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.kosmos.testScope
@@ -39,7 +39,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper(setAsMainLooper = true)
 class SystemUIBottomSheetDialogTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index c8ff20b..624c070 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -18,9 +18,9 @@
 
 import android.os.Handler
 import android.os.PowerManager
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.SysuiTestCase
@@ -51,7 +51,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper(setAsMainLooper = true)
 class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 66211c9..fdf77ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -426,7 +426,7 @@
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
         assertEquals(View.GONE,
-                mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
     }
 
     @Test
@@ -438,7 +438,7 @@
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
         assertEquals(View.VISIBLE,
-                mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
         assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
     }
 
@@ -452,7 +452,7 @@
                 StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
 
         assertEquals(View.GONE,
-                mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
     }
 
     @Test
@@ -465,7 +465,7 @@
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
         assertEquals(View.GONE,
-                mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
     }
 
     @Test
@@ -477,21 +477,21 @@
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
         assertEquals(View.VISIBLE,
-                mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
 
         // Ongoing call ended
         when(mOngoingCallController.hasOngoingCall()).thenReturn(false);
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
         assertEquals(View.GONE,
-                mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
 
         // Ongoing call started
         when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
         assertEquals(View.VISIBLE,
-                mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index 05464f3..4d6798b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -106,7 +106,7 @@
     fun setUp() {
         allowTestableLooperAsMainThread()
         TestableLooper.get(this).runWithLooper {
-            chipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_call_chip, null)
+            chipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip, null)
         }
 
         MockitoAnnotations.initMocks(this)
@@ -206,7 +206,7 @@
                 View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
         )
 
-        assertThat(chipView.findViewById<View>(R.id.ongoing_call_chip_time)?.measuredWidth)
+        assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
                 .isEqualTo(0)
     }
 
@@ -222,7 +222,7 @@
                 View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
         )
 
-        assertThat(chipView.findViewById<View>(R.id.ongoing_call_chip_time)?.measuredWidth)
+        assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
                 .isGreaterThan(0)
     }
 
@@ -237,7 +237,7 @@
                 View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
         )
 
-        assertThat(chipView.findViewById<View>(R.id.ongoing_call_chip_time)?.measuredWidth)
+        assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
                 .isGreaterThan(0)
     }
 
@@ -472,7 +472,10 @@
 
         lateinit var newChipView: View
         TestableLooper.get(this).runWithLooper {
-            newChipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_call_chip, null)
+            newChipView = LayoutInflater.from(mContext).inflate(
+                    R.layout.ongoing_activity_chip,
+                    null
+            )
         }
 
         // Change the chip view associated with the controller.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index 598b12c..eb2538e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -20,6 +20,7 @@
 import android.telephony.SubscriptionManager
 import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
 import android.telephony.TelephonyManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.demomode.DemoMode
@@ -60,7 +61,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -73,7 +73,7 @@
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class MobileRepositorySwitcherTest : SysuiTestCase() {
     private lateinit var underTest: MobileRepositorySwitcher
     private lateinit var realRepo: MobileConnectionsRepositoryImpl
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
index 2654401..237aabc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
 
 import android.telephony.TelephonyManager
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
@@ -44,7 +44,7 @@
 
 @SmallTest
 @OptIn(ExperimentalCoroutinesApi::class)
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
 
     private lateinit var underTest: CarrierMergedConnectionRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index b5525b1..96e599f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
 
+import android.annotation.SuppressLint
 import android.content.Intent
 import android.net.ConnectivityManager
 import android.net.Network
@@ -27,8 +28,10 @@
 import android.net.vcn.VcnTransportInfo
 import android.net.wifi.WifiInfo
 import android.net.wifi.WifiManager
+import android.os.Bundle
 import android.os.ParcelUuid
 import android.telephony.CarrierConfigManager
+import android.telephony.ServiceState
 import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
@@ -53,6 +56,7 @@
 import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
@@ -295,6 +299,50 @@
         }
 
     @Test
+    fun subscriptions_subIsOnlyNtn_modelHasExclusivelyNtnTrue() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.subscriptions)
+
+            val onlyNtnSub =
+                mock<SubscriptionInfo>().also {
+                    whenever(it.isOnlyNonTerrestrialNetwork).thenReturn(true)
+                    whenever(it.subscriptionId).thenReturn(45)
+                    whenever(it.groupUuid).thenReturn(GROUP_1)
+                    whenever(it.carrierName).thenReturn("NTN only")
+                    whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET)
+                }
+
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(onlyNtnSub))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].isExclusivelyNonTerrestrial).isTrue()
+        }
+
+    @Test
+    fun subscriptions_subIsNotOnlyNtn_modelHasExclusivelyNtnFalse() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.subscriptions)
+
+            val notOnlyNtnSub =
+                mock<SubscriptionInfo>().also {
+                    whenever(it.isOnlyNonTerrestrialNetwork).thenReturn(false)
+                    whenever(it.subscriptionId).thenReturn(45)
+                    whenever(it.groupUuid).thenReturn(GROUP_1)
+                    whenever(it.carrierName).thenReturn("NTN only")
+                    whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET)
+                }
+
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(notOnlyNtnSub))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].isExclusivelyNonTerrestrial).isFalse()
+        }
+
+    @Test
     fun testSubscriptions_carrierMergedOnly_listHasCarrierMerged() =
         testScope.runTest {
             val latest by collectLastValue(underTest.subscriptions)
@@ -551,6 +599,51 @@
             assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
         }
 
+    @SuppressLint("UnspecifiedRegisterReceiverFlag")
+    @Test
+    fun testDeviceServiceStateFromBroadcast_eagerlyWatchesBroadcast() =
+        testScope.runTest {
+            // Value starts out empty (null)
+            assertThat(underTest.deviceServiceState.value).isNull()
+
+            // WHEN an appropriate intent gets sent out
+            val intent = serviceStateIntent(subId = -1, emergencyOnly = false)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                intent,
+            )
+            runCurrent()
+
+            // THEN the repo's state is updated
+            val expected = ServiceStateModel(isEmergencyOnly = false)
+            assertThat(underTest.deviceServiceState.value).isEqualTo(expected)
+        }
+
+    @Test
+    fun testDeviceServiceStateFromBroadcast_followsSubIdNegativeOne() =
+        testScope.runTest {
+            // device based state tracks -1
+            val intent = serviceStateIntent(subId = -1, emergencyOnly = false)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                intent,
+            )
+            runCurrent()
+
+            val deviceBasedState = ServiceStateModel(isEmergencyOnly = false)
+            assertThat(underTest.deviceServiceState.value).isEqualTo(deviceBasedState)
+
+            // ... and ignores any other subId
+            val intent2 = serviceStateIntent(subId = 1, emergencyOnly = true)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                intent2,
+            )
+            runCurrent()
+
+            assertThat(underTest.deviceServiceState.value).isEqualTo(deviceBasedState)
+        }
+
     @Test
     @Ignore("b/333912012")
     fun testConnectionCache_clearsInvalidSubscriptions() =
@@ -1447,5 +1540,24 @@
                 whenever(it.transportInfo).thenReturn(WIFI_INFO_ACTIVE)
                 whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(true)
             }
+
+        /**
+         * To properly mimic telephony manager, create a service state, and then turn it into an
+         * intent
+         */
+        private fun serviceStateIntent(
+            subId: Int,
+            emergencyOnly: Boolean = false,
+        ): Intent {
+            val serviceState = ServiceState().apply { isEmergencyOnly = emergencyOnly }
+
+            val bundle = Bundle()
+            serviceState.fillInNotifierBundle(bundle)
+
+            return Intent(Intent.ACTION_SERVICE_STATE).apply {
+                putExtras(bundle)
+                putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId)
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 0b14be1..58d9ee3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
@@ -42,14 +43,11 @@
 import com.google.common.truth.Truth.assertThat
 import java.util.UUID
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.yield
-import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mock
@@ -68,7 +66,7 @@
             set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
         }
 
-    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testDispatcher)
 
     private val tableLogBuffer =
@@ -113,17 +111,12 @@
             )
     }
 
-    @After fun tearDown() {}
-
     @Test
     fun filteredSubscriptions_default() =
         testScope.runTest {
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             assertThat(latest).isEqualTo(listOf<SubscriptionModel>())
-
-            job.cancel()
         }
 
     // Based on the logic from the old pipeline, we'll never filter subs when there are more than 2
@@ -133,12 +126,9 @@
             connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP))
             connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
 
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             assertThat(latest).isEqualTo(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP))
-
-            job.cancel()
         }
 
     @Test
@@ -146,12 +136,9 @@
         testScope.runTest {
             connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
 
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
-
-            job.cancel()
         }
 
     @Test
@@ -160,12 +147,9 @@
             connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
             connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
 
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             assertThat(latest).isEqualTo(listOf(SUB_3_OPP, SUB_4_OPP))
-
-            job.cancel()
         }
 
     @Test
@@ -180,12 +164,9 @@
             connectionsRepository.setSubscriptions(listOf(sub1, sub2))
             connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
 
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             assertThat(latest).isEqualTo(listOf(sub1, sub2))
-
-            job.cancel()
         }
 
     @Test
@@ -202,13 +183,10 @@
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(false)
 
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             // Filtered subscriptions should show the active one when the config is false
             assertThat(latest).isEqualTo(listOf(sub3))
-
-            job.cancel()
         }
 
     @Test
@@ -225,13 +203,10 @@
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(false)
 
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             // Filtered subscriptions should show the active one when the config is false
             assertThat(latest).isEqualTo(listOf(sub4))
-
-            job.cancel()
         }
 
     @Test
@@ -248,14 +223,11 @@
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(true)
 
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             // Filtered subscriptions should show the primary (non-opportunistic) if the config is
             // true
             assertThat(latest).isEqualTo(listOf(sub1))
-
-            job.cancel()
         }
 
     @Test
@@ -272,14 +244,11 @@
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(true)
 
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             // Filtered subscriptions should show the primary (non-opportunistic) if the config is
             // true
             assertThat(latest).isEqualTo(listOf(sub1))
-
-            job.cancel()
         }
 
     @Test
@@ -297,12 +266,9 @@
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(false)
 
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             assertThat(latest).isEqualTo(listOf(sub3))
-
-            job.cancel()
         }
 
     @Test
@@ -320,12 +286,9 @@
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(false)
 
-            var latest: List<SubscriptionModel>? = null
-            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.filteredSubscriptions)
 
             assertThat(latest).isEqualTo(listOf(sub1))
-
-            job.cancel()
         }
 
     @Test
@@ -446,313 +409,345 @@
         }
 
     @Test
+    fun filteredSubscriptions_subNotExclusivelyNonTerrestrial_hasSub() =
+        testScope.runTest {
+            val notExclusivelyNonTerrestrialSub =
+                SubscriptionModel(
+                    isExclusivelyNonTerrestrial = false,
+                    subscriptionId = 5,
+                    carrierName = "Carrier 5",
+                    profileClass = PROFILE_CLASS_UNSET,
+                )
+
+            connectionsRepository.setSubscriptions(listOf(notExclusivelyNonTerrestrialSub))
+
+            val latest by collectLastValue(underTest.filteredSubscriptions)
+
+            assertThat(latest).isEqualTo(listOf(notExclusivelyNonTerrestrialSub))
+        }
+
+    @Test
+    fun filteredSubscriptions_subExclusivelyNonTerrestrial_doesNotHaveSub() =
+        testScope.runTest {
+            val exclusivelyNonTerrestrialSub =
+                SubscriptionModel(
+                    isExclusivelyNonTerrestrial = true,
+                    subscriptionId = 5,
+                    carrierName = "Carrier 5",
+                    profileClass = PROFILE_CLASS_UNSET,
+                )
+
+            connectionsRepository.setSubscriptions(listOf(exclusivelyNonTerrestrialSub))
+
+            val latest by collectLastValue(underTest.filteredSubscriptions)
+
+            assertThat(latest).isEmpty()
+        }
+
+    @Test
+    fun filteredSubscription_mixOfExclusivelyNonTerrestrialAndOther_hasOtherSubsOnly() =
+        testScope.runTest {
+            val exclusivelyNonTerrestrialSub =
+                SubscriptionModel(
+                    isExclusivelyNonTerrestrial = true,
+                    subscriptionId = 5,
+                    carrierName = "Carrier 5",
+                    profileClass = PROFILE_CLASS_UNSET,
+                )
+            val otherSub1 =
+                SubscriptionModel(
+                    isExclusivelyNonTerrestrial = false,
+                    subscriptionId = 1,
+                    carrierName = "Carrier 1",
+                    profileClass = PROFILE_CLASS_UNSET,
+                )
+            val otherSub2 =
+                SubscriptionModel(
+                    isExclusivelyNonTerrestrial = false,
+                    subscriptionId = 2,
+                    carrierName = "Carrier 2",
+                    profileClass = PROFILE_CLASS_UNSET,
+                )
+
+            connectionsRepository.setSubscriptions(
+                listOf(otherSub1, exclusivelyNonTerrestrialSub, otherSub2)
+            )
+
+            val latest by collectLastValue(underTest.filteredSubscriptions)
+
+            assertThat(latest).isEqualTo(listOf(otherSub1, otherSub2))
+        }
+
+    @Test
+    fun filteredSubscriptions_exclusivelyNonTerrestrialSub_andOpportunistic_bothFiltersHappen() =
+        testScope.runTest {
+            // Exclusively non-terrestrial sub
+            val exclusivelyNonTerrestrialSub =
+                SubscriptionModel(
+                    isExclusivelyNonTerrestrial = true,
+                    subscriptionId = 5,
+                    carrierName = "Carrier 5",
+                    profileClass = PROFILE_CLASS_UNSET,
+                )
+
+            // Opportunistic subs
+            val (sub3, sub4) =
+                createSubscriptionPair(
+                    subscriptionIds = Pair(SUB_3_ID, SUB_4_ID),
+                    opportunistic = Pair(true, true),
+                    grouped = true,
+                )
+
+            // WHEN both an exclusively non-terrestrial sub and opportunistic sub pair is included
+            connectionsRepository.setSubscriptions(listOf(sub3, sub4, exclusivelyNonTerrestrialSub))
+            connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+
+            val latest by collectLastValue(underTest.filteredSubscriptions)
+
+            // THEN both the only-non-terrestrial sub and the non-active sub are filtered out,
+            // leaving only sub3.
+            assertThat(latest).isEqualTo(listOf(sub3))
+        }
+
+    @Test
     fun activeDataConnection_turnedOn() =
         testScope.runTest {
             CONNECTION_1.setDataEnabled(true)
-            var latest: Boolean? = null
-            val job =
-                underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+
+            val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun activeDataConnection_turnedOff() =
         testScope.runTest {
             CONNECTION_1.setDataEnabled(true)
-            var latest: Boolean? = null
-            val job =
-                underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
 
             CONNECTION_1.setDataEnabled(false)
-            yield()
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun activeDataConnection_invalidSubId() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job =
-                underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
 
             connectionsRepository.setActiveMobileDataSubscriptionId(INVALID_SUBSCRIPTION_ID)
-            yield()
 
             // An invalid active subId should tell us that data is off
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun failedConnection_default_validated_notFailed() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.mobileIsDefault.value = true
             connectionsRepository.defaultConnectionIsValidated.value = true
-            yield()
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun failedConnection_notDefault_notValidated_notFailed() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.mobileIsDefault.value = false
             connectionsRepository.defaultConnectionIsValidated.value = false
-            yield()
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun failedConnection_default_notValidated_failed() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.mobileIsDefault.value = true
             connectionsRepository.defaultConnectionIsValidated.value = false
-            yield()
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun failedConnection_carrierMergedDefault_notValidated_failed() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.hasCarrierMergedConnection.value = true
             connectionsRepository.defaultConnectionIsValidated.value = false
-            yield()
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     /** Regression test for b/275076959. */
     @Test
     fun failedConnection_dataSwitchInSameGroup_notFailed() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.mobileIsDefault.value = true
             connectionsRepository.defaultConnectionIsValidated.value = true
+            runCurrent()
 
             // WHEN there's a data change in the same subscription group
             connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
             connectionsRepository.defaultConnectionIsValidated.value = false
+            runCurrent()
 
             // THEN the default connection is *not* marked as failed because of forced validation
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun failedConnection_dataSwitchNotInSameGroup_isFailed() =
         testScope.runTest {
-            var latestConnectionFailed: Boolean? = null
-            val job =
-                underTest.isDefaultConnectionFailed
-                    .onEach { latestConnectionFailed = it }
-                    .launchIn(this)
+            val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+
             connectionsRepository.mobileIsDefault.value = true
             connectionsRepository.defaultConnectionIsValidated.value = true
+            runCurrent()
 
             // WHEN the connection is invalidated without a activeSubChangedInGroupEvent
             connectionsRepository.defaultConnectionIsValidated.value = false
 
             // THEN the connection is immediately marked as failed
-            assertThat(latestConnectionFailed).isTrue()
-
-            job.cancel()
+            assertThat(latest).isTrue()
         }
 
     @Test
     fun alwaysShowDataRatIcon_configHasTrue() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.alwaysShowDataRatIcon)
 
             val config = MobileMappings.Config()
             config.alwaysShowDataRatIcon = true
             connectionsRepository.defaultDataSubRatConfig.value = config
-            yield()
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun alwaysShowDataRatIcon_configHasFalse() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.alwaysShowDataRatIcon)
 
             val config = MobileMappings.Config()
             config.alwaysShowDataRatIcon = false
             connectionsRepository.defaultDataSubRatConfig.value = config
-            yield()
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun alwaysUseCdmaLevel_configHasTrue() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.alwaysUseCdmaLevel)
 
             val config = MobileMappings.Config()
             config.alwaysShowCdmaRssi = true
             connectionsRepository.defaultDataSubRatConfig.value = config
-            yield()
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun alwaysUseCdmaLevel_configHasFalse() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.alwaysUseCdmaLevel)
 
             val config = MobileMappings.Config()
             config.alwaysShowCdmaRssi = false
             connectionsRepository.defaultDataSubRatConfig.value = config
-            yield()
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun isSingleCarrier_zeroSubscriptions_false() =
         testScope.runTest {
-            var latest: Boolean? = true
-            val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isSingleCarrier)
 
             connectionsRepository.setSubscriptions(emptyList())
-            assertThat(latest).isFalse()
 
-            job.cancel()
+            assertThat(latest).isFalse()
         }
 
     @Test
     fun isSingleCarrier_oneSubscription_true() =
         testScope.runTest {
-            var latest: Boolean? = false
-            val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isSingleCarrier)
 
             connectionsRepository.setSubscriptions(listOf(SUB_1))
-            assertThat(latest).isTrue()
 
-            job.cancel()
+            assertThat(latest).isTrue()
         }
 
     @Test
     fun isSingleCarrier_twoSubscriptions_false() =
         testScope.runTest {
-            var latest: Boolean? = true
-            val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isSingleCarrier)
 
             connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
-            assertThat(latest).isFalse()
 
-            job.cancel()
+            assertThat(latest).isFalse()
         }
 
     @Test
     fun isSingleCarrier_updates() =
         testScope.runTest {
-            var latest: Boolean? = false
-            val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isSingleCarrier)
 
             connectionsRepository.setSubscriptions(listOf(SUB_1))
             assertThat(latest).isTrue()
 
             connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun mobileIsDefault_mobileFalseAndCarrierMergedFalse_false() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.mobileIsDefault)
 
             connectionsRepository.mobileIsDefault.value = false
             connectionsRepository.hasCarrierMergedConnection.value = false
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun mobileIsDefault_mobileTrueAndCarrierMergedFalse_true() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.mobileIsDefault)
 
             connectionsRepository.mobileIsDefault.value = true
             connectionsRepository.hasCarrierMergedConnection.value = false
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     /** Regression test for b/272586234. */
     @Test
     fun mobileIsDefault_mobileFalseAndCarrierMergedTrue_true() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.mobileIsDefault)
 
             connectionsRepository.mobileIsDefault.value = false
             connectionsRepository.hasCarrierMergedConnection.value = true
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun mobileIsDefault_updatesWhenRepoUpdates() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.mobileIsDefault)
 
             connectionsRepository.mobileIsDefault.value = true
             assertThat(latest).isTrue()
@@ -762,8 +757,6 @@
 
             connectionsRepository.hasCarrierMergedConnection.value = true
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     // The data switch tests are mostly testing the [forcingCellularValidation] flow, but that flow
@@ -772,95 +765,79 @@
     @Test
     fun dataSwitch_inSameGroup_validatedMatchesPreviousValue_expiresAfter2s() =
         testScope.runTest {
-            var latestConnectionFailed: Boolean? = null
-            val job =
-                underTest.isDefaultConnectionFailed
-                    .onEach { latestConnectionFailed = it }
-                    .launchIn(this)
+            val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.mobileIsDefault.value = true
             connectionsRepository.defaultConnectionIsValidated.value = true
+            runCurrent()
 
             // Trigger a data change in the same subscription group that's not yet validated
             connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
             connectionsRepository.defaultConnectionIsValidated.value = false
+            runCurrent()
 
             // After 1s, the force validation bit is still present, so the connection is not marked
             // as failed
             advanceTimeBy(1000)
-            assertThat(latestConnectionFailed).isFalse()
+            assertThat(latest).isFalse()
 
             // After 2s, the force validation expires so the connection updates to failed
             advanceTimeBy(1001)
-            assertThat(latestConnectionFailed).isTrue()
-
-            job.cancel()
+            assertThat(latest).isTrue()
         }
 
     @Test
     fun dataSwitch_inSameGroup_notValidated_immediatelyMarkedAsFailed() =
         testScope.runTest {
-            var latestConnectionFailed: Boolean? = null
-            val job =
-                underTest.isDefaultConnectionFailed
-                    .onEach { latestConnectionFailed = it }
-                    .launchIn(this)
+            val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.mobileIsDefault.value = true
             connectionsRepository.defaultConnectionIsValidated.value = false
+            runCurrent()
 
             connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
 
-            assertThat(latestConnectionFailed).isTrue()
-
-            job.cancel()
+            assertThat(latest).isTrue()
         }
 
     @Test
     fun dataSwitch_loseValidation_thenSwitchHappens_clearsForcedBit() =
         testScope.runTest {
-            var latestConnectionFailed: Boolean? = null
-            val job =
-                underTest.isDefaultConnectionFailed
-                    .onEach { latestConnectionFailed = it }
-                    .launchIn(this)
+            val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             // GIVEN the network starts validated
             connectionsRepository.mobileIsDefault.value = true
             connectionsRepository.defaultConnectionIsValidated.value = true
+            runCurrent()
 
             // WHEN a data change happens in the same group
             connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
 
             // WHEN the validation bit is lost
             connectionsRepository.defaultConnectionIsValidated.value = false
+            runCurrent()
 
             // WHEN another data change happens in the same group
             connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
 
             // THEN the forced validation bit is still used...
-            assertThat(latestConnectionFailed).isFalse()
+            assertThat(latest).isFalse()
 
             advanceTimeBy(1000)
-            assertThat(latestConnectionFailed).isFalse()
+            assertThat(latest).isFalse()
 
             // ... but expires after 2s
             advanceTimeBy(1001)
-            assertThat(latestConnectionFailed).isTrue()
-
-            job.cancel()
+            assertThat(latest).isTrue()
         }
 
     @Test
     fun dataSwitch_whileAlreadyForcingValidation_resetsClock() =
         testScope.runTest {
-            var latestConnectionFailed: Boolean? = null
-            val job =
-                underTest.isDefaultConnectionFailed
-                    .onEach { latestConnectionFailed = it }
-                    .launchIn(this)
+            val latest by collectLastValue(underTest.isDefaultConnectionFailed)
             connectionsRepository.mobileIsDefault.value = true
             connectionsRepository.defaultConnectionIsValidated.value = true
+            runCurrent()
 
             connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
 
@@ -869,44 +846,37 @@
             // WHEN another change in same group event happens
             connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
             connectionsRepository.defaultConnectionIsValidated.value = false
+            runCurrent()
 
             // THEN the forced validation remains for exactly 2 more seconds from now
 
             // 1.500s from second event
             advanceTimeBy(1500)
-            assertThat(latestConnectionFailed).isFalse()
+            assertThat(latest).isFalse()
 
             // 2.001s from the second event
             advanceTimeBy(501)
-            assertThat(latestConnectionFailed).isTrue()
-
-            job.cancel()
+            assertThat(latest).isTrue()
         }
 
     @Test
     fun isForceHidden_repoHasMobileHidden_true() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isForceHidden)
 
             connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun isForceHidden_repoDoesNotHaveMobileHidden_false() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isForceHidden)
 
             connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
@@ -919,6 +889,22 @@
             assertThat(interactor1).isSameInstanceAs(interactor2)
         }
 
+    @Test
+    fun deviceBasedEmergencyMode_emergencyCallsOnly_followsDeviceServiceStateFromRepo() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isDeviceInEmergencyCallsOnlyMode)
+
+            connectionsRepository.deviceServiceState.value =
+                ServiceStateModel(isEmergencyOnly = true)
+
+            assertThat(latest).isTrue()
+
+            connectionsRepository.deviceServiceState.value =
+                ServiceStateModel(isEmergencyOnly = false)
+
+            assertThat(latest).isFalse()
+        }
+
     /**
      * Convenience method for creating a pair of subscriptions to test the filteredSubscriptions
      * flow.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
index 405e3ed..d303976 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
@@ -22,6 +22,7 @@
 import com.android.internal.telephony.flags.Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.log.core.FakeLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository
@@ -71,6 +72,7 @@
                 deviceProvisioningInteractor,
                 wifiInteractor,
                 testScope.backgroundScope,
+                FakeLogBuffer.Factory.create(),
             )
     }
 
@@ -114,6 +116,7 @@
                     deviceProvisioningInteractor,
                     wifiInteractor,
                     testScope.backgroundScope,
+                    FakeLogBuffer.Factory.create(),
                 )
 
             val latest by collectLastValue(underTest.isSatelliteAllowed)
@@ -162,6 +165,7 @@
                     deviceProvisioningInteractor,
                     wifiInteractor,
                     testScope.backgroundScope,
+                    FakeLogBuffer.Factory.create(),
                 )
 
             val latest by collectLastValue(underTest.connectionState)
@@ -218,6 +222,7 @@
                     deviceProvisioningInteractor,
                     wifiInteractor,
                     testScope.backgroundScope,
+                    FakeLogBuffer.Factory.create(),
                 )
 
             val latest by collectLastValue(underTest.signalStrength)
@@ -238,25 +243,97 @@
 
     @Test
     @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    fun areAllConnectionsOutOfService_noConnections_yes() =
+    fun areAllConnectionsOutOfService_noConnections_noDeviceEmergencyCalls_yes() =
         testScope.runTest {
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
 
             // GIVEN, 0 connections
 
+            // GIVEN, device is not in emergency calls only mode
+            iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false
+
             // THEN the value is propagated to this interactor
             assertThat(latest).isTrue()
         }
 
     @Test
     @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    fun areAllConnectionsOutOfService_twoConnectionsOos_nonNtn_yes() =
+    fun areAllConnectionsOutOfService_noConnections_deviceEmergencyCalls_yes() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+            // GIVEN, 0 connections
+
+            // GIVEN, device is in emergency calls only mode
+            iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = true
+
+            // THEN the value is propagated to this interactor
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun areAllConnectionsOutOfService_oneConnectionInService_thenLost_noDeviceEmergencyCalls_yes() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+            // GIVEN, 1 connections
+            val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+            // GIVEN, no device-based emergency calls
+            iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false
+
+            // WHEN connection is in service
+            i1.isInService.value = true
+            i1.isEmergencyOnly.value = false
+            i1.isNonTerrestrial.value = false
+
+            // THEN we are considered NOT to be OOS
+            assertThat(latest).isFalse()
+
+            // WHEN the connection disappears
+            iconsInteractor.icons.value = listOf()
+
+            // THEN we are back to OOS
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun areAllConnectionsOutOfService_oneConnectionInService_thenLost_deviceEmergencyCalls_no() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+            // GIVEN, 1 connections
+            val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+            // GIVEN, device-based emergency calls
+            iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = true
+
+            // WHEN one connection is in service
+            i1.isInService.value = true
+            i1.isEmergencyOnly.value = false
+            i1.isNonTerrestrial.value = false
+
+            // THEN we are considered NOT to be OOS
+            assertThat(latest).isFalse()
+
+            // WHEN the connection disappears
+            iconsInteractor.icons.value = listOf()
+
+            // THEN we are still NOT in OOS, due to device-based emergency calls
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun areAllConnectionsOutOfService_twoConnectionsOos_nonNtn_noDeviceEmergencyCalls_yes() =
         testScope.runTest {
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
 
             // GIVEN, 2 connections
             val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
             val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2)
+            // GIVEN, no device-based emergency calls
+            iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false
 
             // WHEN all of the connections are OOS and none are NTN
             i1.isInService.value = false
@@ -272,13 +349,39 @@
 
     @Test
     @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    fun areAllConnectionsOutOfService_twoConnectionsOos_oneNtn_no() =
+    fun areAllConnectionsOutOfService_twoConnectionsOos_nonNtn_deviceEmergencyCalls_no() =
         testScope.runTest {
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
 
             // GIVEN, 2 connections
             val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
             val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2)
+            // GIVEN, device-based emergency calls
+            iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = true
+
+            // WHEN all of the connections are OOS and none are NTN
+            i1.isInService.value = false
+            i1.isEmergencyOnly.value = false
+            i1.isNonTerrestrial.value = false
+            i2.isInService.value = false
+            i2.isEmergencyOnly.value = false
+            i2.isNonTerrestrial.value = false
+
+            // THEN we are not considered OOS due to device based emergency calling
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun areAllConnectionsOutOfService_twoConnectionsOos_noDeviceEmergencyCalls_oneNtn_no() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+            // GIVEN, 2 connections
+            val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+            val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2)
+            // GIVEN, no device-based emergency calls
+            iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false
 
             // WHEN all of the connections are OOS and one is NTN
             i1.isInService.value = false
@@ -296,12 +399,14 @@
 
     @Test
     @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    fun areAllConnectionsOutOfService_oneConnectionOos_nonNtn_yes() =
+    fun areAllConnectionsOutOfService_oneConnectionOos_noDeviceEmergencyCalls_nonNtn_yes() =
         testScope.runTest {
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
 
             // GIVEN, 1 connection
             val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+            // GIVEN, no device-based emergency calls
+            iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false
 
             // WHEN all of the connections are OOS
             i1.isInService.value = false
@@ -314,7 +419,27 @@
 
     @Test
     @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    fun areAllConnectionsOutOfService_oneConnectionOos_ntn_yes() =
+    fun areAllConnectionsOutOfService_oneConnectionOos_nonNtn_no() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+            // GIVEN, 1 connection
+            val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+            // GIVEN, device-based emergency calls
+            iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = true
+
+            // WHEN all of the connections are OOS
+            i1.isInService.value = false
+            i1.isEmergencyOnly.value = false
+            i1.isNonTerrestrial.value = false
+
+            // THEN the value is propagated to this interactor
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun areAllConnectionsOutOfService_oneConnectionOos_ntn_no() =
         testScope.runTest {
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
 
@@ -416,6 +541,7 @@
                     deviceProvisioningInteractor,
                     wifiInteractor,
                     testScope.backgroundScope,
+                    FakeLogBuffer.Factory.create(),
                 )
 
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index ceaae9e..43b9568 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -75,6 +75,7 @@
                 deviceProvisioningInteractor,
                 wifiInteractor,
                 testScope.backgroundScope,
+                FakeLogBuffer.Factory.create(),
             )
 
         underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
index d1c38f6..0a5e630 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
@@ -22,6 +22,7 @@
 import android.os.UserHandle
 import android.view.View
 import android.view.ViewGroup
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.qs.user.UserSwitchDialogController
@@ -33,14 +34,13 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class BaseUserSwitcherAdapterTest : SysuiTestCase() {
 
     @Mock private lateinit var controller: UserSwitcherController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
index fb4ccb5..c22c628 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
@@ -20,8 +20,8 @@
 import android.content.Context
 import android.content.pm.ServiceInfo
 import android.provider.Settings
-import android.testing.AndroidTestingRunner
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 
 import com.android.systemui.res.R
@@ -60,7 +60,7 @@
 import org.mockito.ArgumentMatchers.anyObject
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class DeviceControlsControllerImplTest : SysuiTestCase() {
 
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
index 2955162..f6e07d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
@@ -29,10 +29,10 @@
 import android.hardware.devicestate.DeviceStateManager;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableContentResolver;
 import android.testing.TestableResources;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
@@ -51,7 +51,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @SmallTest
 public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
index 1c54263..80cc6ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
@@ -20,8 +20,8 @@
 import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.CameraManager
 import android.hardware.camera2.impl.CameraMetadataNative
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import android.testing.AndroidTestingRunner
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.dump.DumpManager
@@ -46,7 +46,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class FlashlightControllerImplTest : SysuiTestCase() {
 
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
index 0bd6a68..9f74915 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
@@ -19,10 +19,10 @@
 import android.content.Context
 import android.content.pm.UserInfo
 import android.graphics.Bitmap
-import android.testing.AndroidTestingRunner
 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
@@ -44,7 +44,7 @@
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class KeyguardUserSwitcherAdapterTest : SysuiTestCase() {
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
index b03edaf..4b14e64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
@@ -23,7 +23,7 @@
 import android.net.Uri
 import android.os.Handler
 import android.safetycenter.SafetyCenterManager
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.any
@@ -44,7 +44,7 @@
 import org.mockito.Mockito.`when`
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class SafetyControllerTest : SysuiTestCase() {
 
     private val TEST_PC_PKG = "testPermissionControllerPackageName"
@@ -188,4 +188,4 @@
 
         assertThat(called).isTrue()
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt
index 3e20f68..81f0950 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt
@@ -22,7 +22,7 @@
 import android.os.Handler
 import android.platform.test.annotations.DisableFlags
 import android.telephony.TelephonyManager
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.server.notification.Flags
 import com.android.systemui.SysuiTestCase
@@ -38,7 +38,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @DisableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
 class SensitiveNotificationProtectionControllerFlagDisabledTest : SysuiTestCase() {
     private val logger = SensitiveNotificationProtectionControllerLogger(logcatLogBuffer())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt
index dbc2e347..0249ab8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.policy
 
 import android.service.quickaccesswallet.QuickAccessWalletClient
-import android.testing.AndroidTestingRunner
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 
 import com.android.systemui.SysuiTestCase
@@ -35,7 +35,7 @@
 import org.mockito.Mockito.`when`
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class WalletControllerImplTest : SysuiTestCase() {
 
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index cfa734a1..ab10bc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -47,7 +47,7 @@
 @SmallTest
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(ParameterizedAndroidJunit4::class)
-class KeyguardStatusBarViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class KeyguardStatusBarViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
@@ -66,7 +66,7 @@
     }
 
     init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
+        mSetFlagsRule.setFlagsParameterization(flags)
     }
 
     @Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index ed7c956..a5e7a67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -995,9 +995,11 @@
                 .setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null));
         // All dynamic colors were added twice: light and dark them
         // All fixed colors were added once
+        // All custom dynamic tokens added twice
         verify(dynamic, times(
                 DynamicColors.allDynamicColorsMapped(false).size() * 2
-                        + DynamicColors.getFixedColorsMapped(false).size())
+                        + DynamicColors.getFixedColorsMapped(false).size()
+                        + DynamicColors.getCustomColorsMapped(false).size() * 2)
         ).setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt
index fd368eb..eaef007 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt
@@ -18,8 +18,11 @@
 
 import android.content.Context
 import android.hardware.display.DisplayManager
+import android.os.HandlerThread
 import android.os.Looper
+import android.os.Process
 import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
 import android.view.Display
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -40,6 +43,7 @@
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
+@RunWithLooper
 class RotationChangeProviderTest : SysuiTestCase() {
 
     private lateinit var rotationChangeProvider: RotationChangeProvider
@@ -48,7 +52,10 @@
     @Mock lateinit var listener: RotationListener
     @Mock lateinit var display: Display
     @Captor lateinit var displayListener: ArgumentCaptor<DisplayManager.DisplayListener>
-    private val fakeHandler = FakeHandler(Looper.getMainLooper())
+    private val bgThread =
+        HandlerThread("UnfoldBgTest", Process.THREAD_PRIORITY_FOREGROUND).apply { start() }
+    private val bgHandler = FakeHandler(bgThread.looper)
+    private val callbackHandler = FakeHandler(Looper.getMainLooper())
 
     private lateinit var spyContext: Context
 
@@ -57,9 +64,10 @@
         MockitoAnnotations.initMocks(this)
         spyContext = spy(context)
         whenever(spyContext.display).thenReturn(display)
-        rotationChangeProvider = RotationChangeProvider(displayManager, spyContext, fakeHandler)
+        rotationChangeProvider =
+            RotationChangeProvider(displayManager, spyContext, bgHandler, callbackHandler)
         rotationChangeProvider.addCallback(listener)
-        fakeHandler.dispatchQueuedMessages()
+        bgHandler.dispatchQueuedMessages()
         verify(displayManager).registerDisplayListener(displayListener.capture(), any())
     }
 
@@ -76,7 +84,7 @@
         verify(listener).onRotationChanged(42)
 
         rotationChangeProvider.removeCallback(listener)
-        fakeHandler.dispatchQueuedMessages()
+        bgHandler.dispatchQueuedMessages()
         sendRotationUpdate(43)
 
         verify(displayManager).unregisterDisplayListener(any())
@@ -86,6 +94,6 @@
     private fun sendRotationUpdate(newRotation: Int) {
         whenever(display.rotation).thenReturn(newRotation)
         displayListener.allValues.forEach { it.onDisplayChanged(display.displayId) }
-        fakeHandler.dispatchQueuedMessages()
+        callbackHandler.dispatchQueuedMessages()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
index 948670f..01795e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.user.domain.interactor
 
 import android.app.admin.DevicePolicyManager
+import android.content.Context
 import android.content.pm.UserInfo
 import android.os.UserHandle
 import android.os.UserManager
@@ -59,6 +60,7 @@
     @Mock private lateinit var switchUser: (Int) -> Unit
     @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
     @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
+    @Mock private lateinit var otherContext: Context
 
     private lateinit var underTest: GuestUserInteractor
 
@@ -74,28 +76,30 @@
         repository = FakeUserRepository()
         repository.setUserInfos(ALL_USERS)
 
-        underTest =
-            GuestUserInteractor(
-                applicationContext = context,
-                applicationScope = scope,
-                mainDispatcher = IMMEDIATE,
-                backgroundDispatcher = IMMEDIATE,
-                manager = manager,
-                repository = repository,
-                deviceProvisionedController = deviceProvisionedController,
-                devicePolicyManager = devicePolicyManager,
-                refreshUsersScheduler =
-                    RefreshUsersScheduler(
-                        applicationScope = scope,
-                        mainDispatcher = IMMEDIATE,
-                        repository = repository,
-                    ),
-                uiEventLogger = uiEventLogger,
-                resumeSessionReceiver = resumeSessionReceiver,
-                resetOrExitSessionReceiver = resetOrExitSessionReceiver,
-            )
+        underTest = initGuestUserInteractor(context)
     }
 
+    private fun initGuestUserInteractor(context: Context) =
+        GuestUserInteractor(
+            applicationContext = context,
+            applicationScope = scope,
+            mainDispatcher = IMMEDIATE,
+            backgroundDispatcher = IMMEDIATE,
+            manager = manager,
+            repository = repository,
+            deviceProvisionedController = deviceProvisionedController,
+            devicePolicyManager = devicePolicyManager,
+            refreshUsersScheduler =
+                RefreshUsersScheduler(
+                    applicationScope = scope,
+                    mainDispatcher = IMMEDIATE,
+                    repository = repository,
+                ),
+            uiEventLogger = uiEventLogger,
+            resumeSessionReceiver = resumeSessionReceiver,
+            resetOrExitSessionReceiver = resetOrExitSessionReceiver,
+        )
+
     @Test
     fun registersBroadcastReceivers() {
         verify(resumeSessionReceiver).register()
@@ -103,6 +107,16 @@
     }
 
     @Test
+    fun registersBroadcastReceiversOnlyForSystemUser() {
+        for (i in 1..5) {
+            whenever(otherContext.userId).thenReturn(UserHandle.MIN_SECONDARY_USER_ID + i)
+            initGuestUserInteractor(otherContext)
+        }
+        verify(resumeSessionReceiver).register()
+        verify(resetOrExitSessionReceiver).register()
+    }
+
+    @Test
     fun onDeviceBootCompleted_allowedToAdd_createGuest() =
         runBlocking(IMMEDIATE) {
             setAllowedToAdd()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index 3dee093..96c6eb8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -887,6 +887,46 @@
     }
 
     @Test
+    fun removeGuestUser_shouldNotShowExitGuestDialog() {
+        createUserInteractor()
+        testScope.runTest {
+            val (userInfo, guestUserInfo) = createUserInfos(count = 2, includeGuest = true)
+            userRepository.setUserInfos(listOf(userInfo, guestUserInfo))
+            userRepository.setSelectedUserInfo(guestUserInfo)
+
+            whenever(manager.markGuestForDeletion(guestUserInfo.id)).thenReturn(true)
+            underTest.removeGuestUser(guestUserInfo.id, userInfo.id)
+            runCurrent()
+
+            verify(manager).markGuestForDeletion(guestUserInfo.id)
+            verify(activityManager).switchUser(userInfo.id)
+            assertThat(collectLastValue(underTest.dialogShowRequests)()).isNull()
+        }
+    }
+
+    @Test
+    fun resetGuestUser_shouldNotShowExitGuestDialog() {
+        createUserInteractor()
+        testScope.runTest {
+            val (userInfo, guestUserInfo) = createUserInfos(count = 2, includeGuest = true)
+            val otherGuestUserInfo = createUserInfos(count = 1, includeGuest = true)[0]
+            userRepository.setUserInfos(listOf(userInfo, guestUserInfo))
+            userRepository.setSelectedUserInfo(guestUserInfo)
+
+            whenever(manager.markGuestForDeletion(guestUserInfo.id)).thenReturn(true)
+            whenever(manager.createGuest(any())).thenReturn(otherGuestUserInfo)
+            underTest.removeGuestUser(guestUserInfo.id, UserHandle.USER_NULL)
+            runCurrent()
+
+            verify(manager).markGuestForDeletion(guestUserInfo.id)
+            verify(manager).createGuest(any())
+            verify(activityManager).switchUser(otherGuestUserInfo.id)
+            assertThat(collectLastValue(underTest.dialogShowRequests)())
+                .isEqualTo(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true))
+        }
+    }
+
+    @Test
     fun showUserSwitcher_fullScreenDisabled_showsDialogSwitcher() {
         createUserInteractor()
         testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt
index 31848a6..e1dcb14 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt
@@ -1,8 +1,8 @@
 package com.android.systemui.util
 
 import android.graphics.Rect
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.wm.shell.common.FloatingContentCoordinator
@@ -14,7 +14,7 @@
 import org.junit.runner.RunWith
 
 @TestableLooper.RunWithLooper
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class FloatingContentCoordinatorTest : SysuiTestCase() {
 
@@ -198,12 +198,11 @@
     }
 
     /**
-     * Helper class that uses [floatingCoordinator.findAreaForContentVertically] to move a
-     * Rect when needed.
+     * Helper class that uses [floatingCoordinator.findAreaForContentVertically] to move a Rect when
+     * needed.
      */
-    inner class FloatingRect(
-        private val underlyingRect: Rect
-    ) : FloatingContentCoordinator.FloatingContent {
+    inner class FloatingRect(private val underlyingRect: Rect) :
+        FloatingContentCoordinator.FloatingContent {
         override fun moveToBounds(bounds: Rect) {
             underlyingRect.set(bounds)
         }
@@ -216,4 +215,4 @@
             return underlyingRect
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt
index 436f5b8..457f2bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt
@@ -19,12 +19,13 @@
 import android.content.BroadcastReceiver
 import android.content.IntentFilter
 import android.os.UserHandle
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import androidx.lifecycle.Observer
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
+import java.util.concurrent.Executor
 import org.junit.After
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -38,10 +39,9 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class RingerModeLiveDataTest : SysuiTestCase() {
 
@@ -52,16 +52,11 @@
         private val INTENT = "INTENT"
     }
 
-    @Mock
-    private lateinit var broadcastDispatcher: BroadcastDispatcher
-    @Mock
-    private lateinit var valueSupplier: () -> Int
-    @Mock
-    private lateinit var observer: Observer<Int>
-    @Captor
-    private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver>
-    @Captor
-    private lateinit var intentFilterCaptor: ArgumentCaptor<IntentFilter>
+    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock private lateinit var valueSupplier: () -> Int
+    @Mock private lateinit var observer: Observer<Int>
+    @Captor private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver>
+    @Captor private lateinit var intentFilterCaptor: ArgumentCaptor<IntentFilter>
 
     // Run everything immediately
     private val executor = Executor { it.run() }
@@ -88,14 +83,14 @@
     fun testOnActive_broadcastRegistered() {
         liveData.observeForever(observer)
         verify(broadcastDispatcher)
-                .registerReceiver(any(), any(), eq(executor), eq(UserHandle.ALL), anyInt(), any())
+            .registerReceiver(any(), any(), eq(executor), eq(UserHandle.ALL), anyInt(), any())
     }
 
     @Test
     fun testOnActive_intentFilterHasIntent() {
         liveData.observeForever(observer)
-        verify(broadcastDispatcher).registerReceiver(any(), capture(intentFilterCaptor), any(),
-                any(), anyInt(), any())
+        verify(broadcastDispatcher)
+            .registerReceiver(any(), capture(intentFilterCaptor), any(), any(), anyInt(), any())
         assertTrue(intentFilterCaptor.value.hasAction(INTENT))
     }
 
@@ -111,4 +106,4 @@
         liveData.removeObserver(observer)
         verify(broadcastDispatcher).unregisterReceiver(any())
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
index b13cb72..6271904 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
@@ -19,10 +19,10 @@
 import android.app.WallpaperInfo
 import android.app.WallpaperManager
 import android.os.IBinder
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.View
 import android.view.ViewRootImpl
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.eq
@@ -32,36 +32,30 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.any
 import org.mockito.Mockito.anyFloat
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.doThrow
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.Mockito.`when`
 import org.mockito.junit.MockitoJUnit
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 @SmallTest
 class WallpaperControllerTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var wallpaperManager: WallpaperManager
-    @Mock
-    private lateinit var root: View
-    @Mock
-    private lateinit var viewRootImpl: ViewRootImpl
-    @Mock
-    private lateinit var windowToken: IBinder
+    @Mock private lateinit var wallpaperManager: WallpaperManager
+    @Mock private lateinit var root: View
+    @Mock private lateinit var viewRootImpl: ViewRootImpl
+    @Mock private lateinit var windowToken: IBinder
     private val wallpaperRepository = FakeWallpaperRepository()
 
-    @JvmField
-    @Rule
-    val mockitoRule = MockitoJUnit.rule()
+    @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
 
     private lateinit var wallaperController: WallpaperController
 
@@ -92,9 +86,7 @@
 
     @Test
     fun setUnfoldTransitionZoom_defaultUnfoldTransitionIsDisabled_doesNotUpdateWallpaperZoom() {
-        wallpaperRepository.wallpaperInfo.value = createWallpaperInfo(
-            useDefaultTransition = false
-        )
+        wallpaperRepository.wallpaperInfo.value = createWallpaperInfo(useDefaultTransition = false)
 
         wallaperController.setUnfoldTransitionZoom(0.5f)
 
@@ -130,7 +122,8 @@
 
     @Test
     fun setNotificationZoom_exceptionWhenUpdatingZoom_doesNotFail() {
-        doThrow(IllegalArgumentException("test exception")).`when`(wallpaperManager)
+        doThrow(IllegalArgumentException("test exception"))
+            .`when`(wallpaperManager)
             .setWallpaperZoomOut(any(), anyFloat())
 
         wallaperController.setNotificationShadeZoom(0.5f)
@@ -140,8 +133,7 @@
 
     private fun createWallpaperInfo(useDefaultTransition: Boolean = true): WallpaperInfo {
         val info = mock(WallpaperInfo::class.java)
-        whenever(info.shouldUseDefaultUnfoldTransition())
-            .thenReturn(useDefaultTransition)
+        whenever(info.shouldUseDefaultUnfoldTransition()).thenReturn(useDefaultTransition)
         return info
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt
index 92afb03..b26598c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt
@@ -16,13 +16,16 @@
 
 package com.android.systemui.util.animation
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
-import org.junit.Test
 import java.lang.IllegalArgumentException
+import org.junit.runner.RunWith
+import org.junit.Test
 
 @SmallTest
+@RunWith(AndroidJUnit4::class)
 class AnimationUtilTest : SysuiTestCase() {
     @Test
     fun getMsForFrames_5frames_returns83() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java
index 9dfa14d..7dfac0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java
@@ -22,8 +22,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import android.testing.AndroidTestingRunner;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -39,7 +38,7 @@
 import java.util.List;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class FakeExecutorTest extends SysuiTestCase {
     @Before
     public void setUp() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java
index 78fc680..48fb745 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java
@@ -24,8 +24,7 @@
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 
-import android.testing.AndroidTestingRunner;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -39,7 +38,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class MessageRouterImplTest extends SysuiTestCase {
     private static final int MESSAGE_A = 0;
     private static final int MESSAGE_B = 1;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt
index d1d2598..7ec420f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt
@@ -15,7 +15,7 @@
  */
 package com.android.systemui.util.concurrency
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.time.FakeSystemClock
@@ -26,7 +26,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class MockExecutorHandlerTest : SysuiTestCase() {
     /** Test FakeExecutor that receives non-delayed items to execute. */
     @Test
@@ -141,6 +141,88 @@
         assertEquals(3, runnable.mRunCount)
     }
 
+    @Test
+    fun testRemoveCallback_postDelayed() {
+        val clock = FakeSystemClock()
+        val fakeExecutor = FakeExecutor(clock)
+        val handler = mockExecutorHandler(fakeExecutor)
+        val runnable = RunnableImpl()
+
+        handler.postDelayed(runnable, 50)
+        handler.postDelayed(runnable, 150)
+        fakeExecutor.advanceClockToNext()
+        fakeExecutor.runAllReady()
+
+        assertEquals(1, runnable.mRunCount)
+        assertEquals(1, fakeExecutor.numPending())
+
+        handler.removeCallbacks(runnable)
+        assertEquals(0, fakeExecutor.numPending())
+
+        assertEquals(1, runnable.mRunCount)
+    }
+
+    @Test
+    fun testRemoveCallback_postAtTime() {
+        val clock = FakeSystemClock()
+        val fakeExecutor = FakeExecutor(clock)
+        val handler = mockExecutorHandler(fakeExecutor)
+        val runnable = RunnableImpl()
+        assertEquals(10000, clock.uptimeMillis())
+
+        handler.postAtTime(runnable, 10050)
+        handler.postAtTime(runnable, 10150)
+        fakeExecutor.advanceClockToNext()
+        fakeExecutor.runAllReady()
+
+        assertEquals(1, runnable.mRunCount)
+        assertEquals(1, fakeExecutor.numPending())
+
+        handler.removeCallbacks(runnable)
+        assertEquals(0, fakeExecutor.numPending())
+
+        assertEquals(1, runnable.mRunCount)
+    }
+
+    @Test
+    fun testRemoveCallback_mixed_allRemoved() {
+        val clock = FakeSystemClock()
+        val fakeExecutor = FakeExecutor(clock)
+        val handler = mockExecutorHandler(fakeExecutor)
+        val runnable = RunnableImpl()
+        assertEquals(10000, clock.uptimeMillis())
+
+        handler.postAtTime(runnable, 10050)
+        handler.postDelayed(runnable, 150)
+
+        handler.removeCallbacks(runnable)
+        assertEquals(0, fakeExecutor.numPending())
+
+        fakeExecutor.advanceClockToLast()
+        fakeExecutor.runAllReady()
+        assertEquals(0, runnable.mRunCount)
+    }
+
+    @Test
+    fun testRemoveCallback_differentRunnables_onlyMatchingRemoved() {
+        val clock = FakeSystemClock()
+        val fakeExecutor = FakeExecutor(clock)
+        val handler = mockExecutorHandler(fakeExecutor)
+        val runnable1 = RunnableImpl()
+        val runnable2 = RunnableImpl()
+
+        handler.postDelayed(runnable1, 50)
+        handler.postDelayed(runnable2, 150)
+
+        handler.removeCallbacks(runnable1)
+        assertEquals(1, fakeExecutor.numPending())
+
+        fakeExecutor.advanceClockToLast()
+        fakeExecutor.runAllReady()
+        assertEquals(0, runnable1.mRunCount)
+        assertEquals(1, runnable2.mRunCount)
+    }
+
     /**
      * Verifies that `Handler.removeMessages`, which doesn't make sense with executor backing,
      * causes an error in the test (rather than failing silently like most mocks).
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java
index 00f37ae..13fff29d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java
@@ -18,8 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.testing.AndroidTestingRunner;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -30,7 +29,7 @@
 import org.junit.runner.RunWith;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class RepeatableExecutorTest extends SysuiTestCase {
 
     private static final int DELAY = 100;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java
index b367a60..37015e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java
@@ -23,8 +23,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.testing.AndroidTestingRunner;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.CoreStartable;
@@ -44,7 +43,7 @@
 import java.util.Set;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class ConditionalCoreStartableTest extends SysuiTestCase {
     public static class FakeConditionalCoreStartable extends ConditionalCoreStartable {
         interface Callback {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt
index ac357ea..b8f5815 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt
@@ -4,7 +4,7 @@
 import android.graphics.Bitmap
 import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.ShapeDrawable
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
@@ -12,7 +12,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class DrawableSizeTest : SysuiTestCase() {
 
@@ -32,14 +32,11 @@
 
     @Test
     fun testDownscaleToSize_drawableSmallerThanRequirement_unchanged() {
-        val drawable = BitmapDrawable(resources,
-                Bitmap.createBitmap(
-                        resources.displayMetrics,
-                        150,
-                        150,
-                        Bitmap.Config.ARGB_8888
-                )
-        )
+        val drawable =
+            BitmapDrawable(
+                resources,
+                Bitmap.createBitmap(resources.displayMetrics, 150, 150, Bitmap.Config.ARGB_8888)
+            )
         val result = DrawableSize.downscaleToSize(resources, drawable, 300, 300)
         assertThat(result).isSameInstanceAs(drawable)
     }
@@ -48,14 +45,11 @@
     fun testDownscaleToSize_drawableLargerThanRequirementWithDensity_resized() {
         // This bitmap would actually fail to resize if the method doesn't check for
         // bitmap dimensions inside drawable.
-        val drawable = BitmapDrawable(resources,
-                Bitmap.createBitmap(
-                        resources.displayMetrics,
-                        150,
-                        75,
-                        Bitmap.Config.ARGB_8888
-                )
-        )
+        val drawable =
+            BitmapDrawable(
+                resources,
+                Bitmap.createBitmap(resources.displayMetrics, 150, 75, Bitmap.Config.ARGB_8888)
+            )
 
         val result = DrawableSize.downscaleToSize(resources, drawable, 75, 75)
         assertThat(result).isNotSameInstanceAs(drawable)
@@ -65,9 +59,9 @@
 
     @Test
     fun testDownscaleToSize_drawableAnimated_unchanged() {
-        val drawable = resources.getDrawable(android.R.drawable.stat_sys_download,
-                resources.newTheme())
+        val drawable =
+            resources.getDrawable(android.R.drawable.stat_sys_download, resources.newTheme())
         val result = DrawableSize.downscaleToSize(resources, drawable, 1, 1)
         assertThat(result).isSameInstanceAs(drawable)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
index 7d0d57b..e2ce50c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.util.kotlin
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.time.FakeSystemClock
@@ -47,7 +47,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class PairwiseFlowTest : SysuiTestCase() {
     @Test
     fun simple() = runBlocking {
@@ -89,7 +89,9 @@
                         initRun = true
                         "initial"
                     }
-                ) { prev: String, next: String -> "$prev|$next" }
+                ) { prev: String, next: String ->
+                    "$prev|$next"
+                }
             )
             .emitsExactly("initial|val1", "val1|val2")
         assertThat(initRun).isTrue()
@@ -104,7 +106,9 @@
                         initRun = true
                         "initial"
                     }
-                ) { prev: String, next: String -> "$prev|$next" }
+                ) { prev: String, next: String ->
+                    "$prev|$next"
+                }
             )
             .emitsNothing()
         // Even though the flow will not emit anything, the initial value function should still get
@@ -120,7 +124,9 @@
                 initRun = true
                 "initial"
             }
-        ) { prev: String, next: String -> "$prev|$next" }
+        ) { prev: String, next: String ->
+            "$prev|$next"
+        }
 
         // Since the flow isn't collected, ensure [initialValueFun] isn't run.
         assertThat(initRun).isFalse()
@@ -146,7 +152,7 @@
 }
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class SetChangesFlowTest : SysuiTestCase() {
     @Test
     fun simple() = runBlocking {
@@ -198,7 +204,7 @@
 }
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class SampleFlowTest : SysuiTestCase() {
     @Test
     fun simple() = runBlocking {
@@ -240,7 +246,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class ThrottleFlowTest : SysuiTestCase() {
 
     @Test
@@ -248,13 +254,16 @@
         // Arrange
         val choreographer = createChoreographer(this)
         val output = mutableListOf<Int>()
-        val collectJob = backgroundScope.launch {
-            flow {
-                emit(1)
-                delay(1000)
-                emit(2)
-            }.throttle(1000, choreographer.fakeClock).toList(output)
-        }
+        val collectJob =
+            backgroundScope.launch {
+                flow {
+                        emit(1)
+                        delay(1000)
+                        emit(2)
+                    }
+                    .throttle(1000, choreographer.fakeClock)
+                    .toList(output)
+            }
 
         // Act
         choreographer.advanceAndRun(0)
@@ -283,13 +292,16 @@
         // Arrange
         val choreographer = createChoreographer(this)
         val output = mutableListOf<Int>()
-        val collectJob = backgroundScope.launch {
-            flow {
-                emit(1)
-                delay(500)
-                emit(2)
-            }.throttle(1000, choreographer.fakeClock).toList(output)
-        }
+        val collectJob =
+            backgroundScope.launch {
+                flow {
+                        emit(1)
+                        delay(500)
+                        emit(2)
+                    }
+                    .throttle(1000, choreographer.fakeClock)
+                    .toList(output)
+            }
 
         // Act
         choreographer.advanceAndRun(0)
@@ -319,15 +331,18 @@
         // Arrange
         val choreographer = createChoreographer(this)
         val output = mutableListOf<Int>()
-        val collectJob = backgroundScope.launch {
-            flow {
-                emit(1)
-                delay(500)
-                emit(2)
-                delay(500)
-                emit(3)
-            }.throttle(1000, choreographer.fakeClock).toList(output)
-        }
+        val collectJob =
+            backgroundScope.launch {
+                flow {
+                        emit(1)
+                        delay(500)
+                        emit(2)
+                        delay(500)
+                        emit(3)
+                    }
+                    .throttle(1000, choreographer.fakeClock)
+                    .toList(output)
+            }
 
         // Act
         choreographer.advanceAndRun(0)
@@ -357,15 +372,18 @@
         // Arrange
         val choreographer = createChoreographer(this)
         val output = mutableListOf<Int>()
-        val collectJob = backgroundScope.launch {
-            flow {
-                emit(1)
-                delay(500)
-                emit(2)
-                delay(250)
-                emit(3)
-            }.throttle(1000, choreographer.fakeClock).toList(output)
-        }
+        val collectJob =
+            backgroundScope.launch {
+                flow {
+                        emit(1)
+                        delay(500)
+                        emit(2)
+                        delay(250)
+                        emit(3)
+                    }
+                    .throttle(1000, choreographer.fakeClock)
+                    .toList(output)
+            }
 
         // Act
         choreographer.advanceAndRun(0)
@@ -391,15 +409,16 @@
         collectJob.cancel()
     }
 
-    private fun createChoreographer(testScope: TestScope) = object {
-        val fakeClock = FakeSystemClock()
+    private fun createChoreographer(testScope: TestScope) =
+        object {
+            val fakeClock = FakeSystemClock()
 
-        fun advanceAndRun(millis: Long) {
-            fakeClock.advanceTime(millis)
-            testScope.advanceTimeBy(millis)
-            testScope.runCurrent()
+            fun advanceAndRun(millis: Long) {
+                fakeClock.advanceTime(millis)
+                testScope.advanceTimeBy(millis)
+                testScope.runCurrent()
+            }
         }
-    }
 }
 
 private fun <T> assertThatFlow(flow: Flow<T>) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
index 4ca1fd3..c31b287 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.util.kotlin
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import java.util.concurrent.atomic.AtomicLong
@@ -31,43 +31,42 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class IpcSerializerTest : SysuiTestCase() {
 
     private val serializer = IpcSerializer()
 
     @Ignore("b/253046405")
     @Test
-    fun serializeManyIncomingIpcs(): Unit = runBlocking(Dispatchers.Main.immediate) {
-        val processor = launch(start = CoroutineStart.LAZY) { serializer.process() }
-        withContext(Dispatchers.IO) {
-            val lastEvaluatedTime = AtomicLong(System.currentTimeMillis())
-            // First, launch many serialization requests in parallel
-            repeat(100_000) {
-                launch(Dispatchers.Unconfined) {
-                    val enqueuedTime = System.currentTimeMillis()
-                    serializer.runSerialized {
-                        val last = lastEvaluatedTime.getAndSet(enqueuedTime)
-                        assertTrue(
-                            "expected $last less than or equal to $enqueuedTime ",
-                            last <= enqueuedTime,
-                        )
+    fun serializeManyIncomingIpcs(): Unit =
+        runBlocking(Dispatchers.Main.immediate) {
+            val processor = launch(start = CoroutineStart.LAZY) { serializer.process() }
+            withContext(Dispatchers.IO) {
+                val lastEvaluatedTime = AtomicLong(System.currentTimeMillis())
+                // First, launch many serialization requests in parallel
+                repeat(100_000) {
+                    launch(Dispatchers.Unconfined) {
+                        val enqueuedTime = System.currentTimeMillis()
+                        serializer.runSerialized {
+                            val last = lastEvaluatedTime.getAndSet(enqueuedTime)
+                            assertTrue(
+                                "expected $last less than or equal to $enqueuedTime ",
+                                last <= enqueuedTime,
+                            )
+                        }
                     }
                 }
+                // Then, process them all in the order they came in.
+                processor.start()
             }
-            // Then, process them all in the order they came in.
-            processor.start()
+            // All done, stop processing
+            processor.cancel()
         }
-        // All done, stop processing
-        processor.cancel()
-    }
 
     @Test(timeout = 5000)
     fun serializeOnOneThread_doesNotDeadlock() = runBlocking {
         val job = launch { serializer.process() }
-        repeat(100) {
-            serializer.runSerializedBlocking { }
-        }
+        repeat(100) { serializer.runSerializedBlocking {} }
         job.cancel()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt
index 2013bb0..8bfff9c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt
@@ -27,13 +27,13 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.junit.runners.Parameterized.Parameters
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 internal class PackageManagerExtComponentEnabledTest(private val testCase: TestCase) :
     SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt
index 6848b83..b2f7c1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.util.kotlin
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
@@ -28,7 +28,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class RaceSuspendTest : SysuiTestCase() {
     @Test
     fun raceSimple() = runBlocking {
@@ -46,10 +46,11 @@
     @Test
     fun raceImmediate() = runBlocking {
         assertThat(
-            race<Int>(
-                { 1 },
-                { 2 },
+                race<Int>(
+                    { 1 },
+                    { 2 },
+                )
             )
-        ).isEqualTo(1)
+            .isEqualTo(1)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java
index 84129be..300c298 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java
@@ -26,9 +26,9 @@
 
 import android.content.res.Resources;
 import android.hardware.Sensor;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -46,7 +46,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class PostureDependentProximitySensorTest extends SysuiTestCase {
     @Mock private Resources mResources;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
index 19dbf9a..5dd008a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
@@ -23,9 +23,9 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -40,7 +40,7 @@
 import java.util.function.Consumer;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class ProximityCheckTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java
index 5e75578..0eab74e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java
@@ -24,9 +24,9 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -40,7 +40,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class ProximitySensorImplDualTest extends SysuiTestCase {
     private ProximitySensor mProximitySensor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java
index 752cd32..f44c842 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java
@@ -21,9 +21,9 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -40,7 +40,7 @@
  * Tests for ProximitySensor that rely on a single hardware sensor.
  */
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class ProximitySensorImplSingleTest extends SysuiTestCase {
     private ProximitySensor mProximitySensor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
index 8d26c87..a54afad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
@@ -30,8 +30,8 @@
 import android.content.pm.UserInfo;
 import android.os.IBinder;
 import android.os.UserHandle;
-import android.testing.AndroidTestingRunner;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -49,7 +49,7 @@
 import java.util.Objects;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class ObservableServiceConnectionTest extends SysuiTestCase {
     static class Foo {
         int mValue;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java
index a2fd288..a70b00c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java
@@ -24,8 +24,8 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.testing.AndroidTestingRunner;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -38,7 +38,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class PackageObserverTest extends SysuiTestCase {
     @Mock
     Context mContext;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
index 55c49ee..ef10fdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
@@ -19,8 +19,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
-import android.testing.AndroidTestingRunner;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -37,7 +36,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class PersistentConnectionManagerTest extends SysuiTestCase {
     private static final int MAX_RETRIES = 5;
     private static final int RETRY_DELAY_MS = 1000;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
index f65caee2..99f6303 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
@@ -28,8 +28,8 @@
 import android.database.ContentObserver;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.testing.AndroidTestingRunner;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -44,7 +44,7 @@
 import java.util.Map;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class FakeSettingsTest extends SysuiTestCase {
     @Mock
     ContentObserver mContentObserver;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
index 913759f..88b2630 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.util.settings.repository
 
 import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
@@ -33,11 +34,10 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() {
 
     private val dispatcher = StandardTestDispatcher()
@@ -48,11 +48,12 @@
 
     @Before
     fun setup() {
-        repository = UserAwareSecureSettingsRepositoryImpl(
-            secureSettings,
-            userRepository,
-            dispatcher,
-        )
+        repository =
+            UserAwareSecureSettingsRepositoryImpl(
+                secureSettings,
+                userRepository,
+                dispatcher,
+            )
         userRepository.setUserInfos(USER_INFOS)
         setSettingValueForUser(enabled = true, userInfo = SETTING_ENABLED_USER)
         setSettingValueForUser(enabled = false, userInfo = SETTING_DISABLED_USER)
@@ -105,4 +106,4 @@
         val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
         val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
index 94100fe..6637d5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
@@ -17,7 +17,7 @@
 
 package com.android.systemui.util.ui
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
@@ -30,7 +30,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class AnimatedValueTest : SysuiTestCase() {
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt
index e3cd9b2..3dcb828 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt
@@ -19,17 +19,20 @@
 import android.graphics.Rect
 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.util.mockito.any
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.Mockito.doAnswer
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.`when`
 
 @SmallTest
+@RunWith(AndroidJUnit4::class)
 class ViewUtilTest : SysuiTestCase() {
     private val viewUtil = ViewUtil()
     private lateinit var view: View
@@ -45,11 +48,13 @@
         location[1] = VIEW_TOP
         `when`(view.locationOnScreen).thenReturn(location)
         doAnswer { invocation ->
-            val pos = invocation.arguments[0] as IntArray
-            pos[0] = VIEW_LEFT
-            pos[1] = VIEW_TOP
-            null
-        }.`when`(view).getLocationInWindow(any())
+                val pos = invocation.arguments[0] as IntArray
+                pos[0] = VIEW_LEFT
+                pos[1] = VIEW_TOP
+                null
+            }
+            .`when`(view)
+            .getLocationInWindow(any())
     }
 
     @Test
@@ -59,9 +64,8 @@
 
     @Test
     fun touchIsWithinView_onTopLeftCorner_returnsTrue() {
-        assertThat(viewUtil.touchIsWithinView(
-            view, VIEW_LEFT.toFloat(), VIEW_TOP.toFloat())
-        ).isTrue()
+        assertThat(viewUtil.touchIsWithinView(view, VIEW_LEFT.toFloat(), VIEW_TOP.toFloat()))
+            .isTrue()
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
index ed07ad2..207c35d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
@@ -35,15 +35,18 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
 
 import java.util.List;
 
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
+
 @SmallTest
-@RunWith(Parameterized.class)
+@RunWith(ParameterizedAndroidJunit4.class)
 public class WakeLockTest extends SysuiTestCase {
 
-    @Parameterized.Parameters(name = "{0}")
+    @Parameters(name = "{0}")
     public static List<FlagsParameterization> getFlags() {
         return FlagsParameterization.allCombinationsOf(
                 Flags.FLAG_DELAYED_WAKELOCK_RELEASE_ON_BACKGROUND_THREAD);
@@ -114,4 +117,4 @@
         // shouldn't throw an exception on production builds
         mWakeLock.release(WHY);
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 3b468aa..9864439 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -65,7 +65,6 @@
 import android.widget.SeekBar;
 
 import androidx.test.core.view.MotionEventBuilder;
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.jank.InteractionJankMonitor;
@@ -273,54 +272,30 @@
 
     @Test
     @DisableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
-    public void testVolumeChange_noSliderHaptics_doesNotDeliverOnProgressChangedHaptics() {
-        final State shellState = createShellState();
-        VolumeDialogController.StreamState musicStreamState =
-                shellState.states.get(AudioSystem.STREAM_MUSIC);
+    public void addSliderHaptics_withHapticsDisabled_doesNotDeliverOnProgressChangedHaptics() {
+        // GIVEN that the slider haptics flag is disabled and we try to add haptics to volume rows
+        mDialog.addSliderHapticsToRows();
 
-        mDialog.show(SHOW_REASON_UNKNOWN);
-        mTestableLooper.processMessages(1); //Only the SHOW message
-        mDialog.removeDismissMessages(); // Temporarily remove the rescheduled DISMISS
+        // WHEN haptics try to be delivered to a volume stream
+        boolean canDeliverHaptics =
+                mDialog.canDeliverProgressHapticsToStream(AudioSystem.STREAM_MUSIC, true, 50);
 
-        // Change the volume two times
-        musicStreamState.level += 10;
-        mDialog.onStateChangedH(shellState);
-        musicStreamState.level += 10;
-        mDialog.onStateChangedH(shellState);
-
-        // expected: the type of the latest progress haptics for the stream should be DISABLED
-        int type = mDialog.progressHapticsForStream(AudioSystem.STREAM_MUSIC);
-        assertEquals(VolumeDialogImpl.PROGRESS_HAPTICS_DISABLED, type);
-
-        mDialog.dismiss(DISMISS_REASON_UNKNOWN); // Dismiss
-        mTestableLooper.processAllMessages();
+        // THEN the result is that haptics are not successfully delivered
+        assertFalse(canDeliverHaptics);
     }
 
-    @Test @FlakyTest(bugId = 329099861)
+    @Test
     @EnableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
-    public void testVolumeChange_withSliderHaptics_deliversOnProgressChangedHapticsEagerly() {
-        // create haptic plugins on the rows with the flag enabled
+    public void addSliderHaptics_withHapticsEnabled_canDeliverOnProgressChangedHaptics() {
+        // GIVEN that the slider haptics flag is enabled and we try to add haptics to volume rows
         mDialog.addSliderHapticsToRows();
-        final State shellState = createShellState();
-        VolumeDialogController.StreamState musicStreamState =
-                shellState.states.get(AudioSystem.STREAM_MUSIC);
 
-        mDialog.show(SHOW_REASON_UNKNOWN);
-        mTestableLooper.processMessages(1); //Only the SHOW message
-        mDialog.removeDismissMessages(); // Temporarily remove the rescheduled DISMISS
+        // WHEN haptics try to be delivered to a volume stream
+        boolean canDeliverHaptics =
+                mDialog.canDeliverProgressHapticsToStream(AudioSystem.STREAM_MUSIC, true, 50);
 
-        // Change the volume two times
-        musicStreamState.level += 10;
-        mDialog.onStateChangedH(shellState);
-        musicStreamState.level += 10;
-        mDialog.onStateChangedH(shellState);
-
-        // expected: the type of the latest progress haptics for the stream should be EAGER
-        int type = mDialog.progressHapticsForStream(AudioSystem.STREAM_MUSIC);
-        assertEquals(VolumeDialogImpl.PROGRESS_HAPTICS_EAGER, type);
-
-        mDialog.dismiss(DISMISS_REASON_UNKNOWN); // Dismiss
-        mTestableLooper.processAllMessages();
+        // THEN the result is that haptics are successfully delivered
+        assertTrue(canDeliverHaptics);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 56e5e29..daea7b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -27,6 +27,7 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
+import static com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -138,7 +139,6 @@
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -467,7 +467,8 @@
                         mock(UiEventLogger.class),
                         mock(UserTracker.class),
                         mock(AvalancheProvider.class),
-                        mock(SystemSettings.class)
+                        mock(SystemSettings.class),
+                        mock(PackageManager.class)
                         );
         interruptionDecisionProvider.start();
 
@@ -2142,6 +2143,112 @@
         assertThat(mBubbleController.getLayerView().isExpanded()).isFalse();
     }
 
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void dragBubbleBarBubble_selectedBubble_expandedViewCollapsesDuringDrag() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        // Add 2 bubbles
+        mEntryListener.onEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow2);
+        mBubbleController.updateBubble(mBubbleEntry);
+        mBubbleController.updateBubble(mBubbleEntry2);
+
+        // Select first bubble
+        mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), new Rect());
+        assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey());
+        assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+
+        // Drag first bubble, bubble should collapse
+        mBubbleController.startBubbleDrag(mBubbleEntry.getKey());
+        assertThat(mBubbleController.getLayerView().isExpanded()).isFalse();
+
+        // Stop dragging, first bubble should be expanded
+        mBubbleController.stopBubbleDrag(BubbleBarLocation.LEFT);
+        assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey());
+        assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+    }
+
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void dragBubbleBarBubble_unselectedBubble_expandedViewCollapsesDuringDrag() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        // Add 2 bubbles
+        mEntryListener.onEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow2);
+        mBubbleController.updateBubble(mBubbleEntry);
+        mBubbleController.updateBubble(mBubbleEntry2);
+
+        // Select first bubble
+        mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), new Rect());
+        assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey());
+        assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+
+        // Drag second bubble, bubble should collapse
+        mBubbleController.startBubbleDrag(mBubbleEntry2.getKey());
+        assertThat(mBubbleController.getLayerView().isExpanded()).isFalse();
+
+        // Stop dragging, first bubble should be expanded
+        mBubbleController.stopBubbleDrag(BubbleBarLocation.LEFT);
+        assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey());
+        assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+    }
+
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void dismissBubbleBarBubble_selected_selectsAndExpandsNext() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        // Add 2 bubbles
+        mEntryListener.onEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow2);
+        mBubbleController.updateBubble(mBubbleEntry);
+        mBubbleController.updateBubble(mBubbleEntry2);
+
+        // Select first bubble
+        mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), new Rect());
+        // Drag first bubble to dismiss
+        mBubbleController.startBubbleDrag(mBubbleEntry.getKey());
+        mBubbleController.dragBubbleToDismiss(mBubbleEntry.getKey());
+        // Second bubble is selected and expanded
+        assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry2.getKey());
+        assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+    }
+
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void dismissBubbleBarBubble_unselected_selectionDoesNotChange() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        // Add 2 bubbles
+        mEntryListener.onEntryAdded(mRow);
+        mEntryListener.onEntryAdded(mRow2);
+        mBubbleController.updateBubble(mBubbleEntry);
+        mBubbleController.updateBubble(mBubbleEntry2);
+
+        // Select first bubble
+        mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), new Rect());
+        // Drag second bubble to dismiss
+        mBubbleController.startBubbleDrag(mBubbleEntry2.getKey());
+        mBubbleController.dragBubbleToDismiss(mBubbleEntry2.getKey());
+        // First bubble remains selected and expanded
+        assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey());
+        assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+    }
+
     @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
     @Test
     public void doesNotRegisterSensitiveStateListener() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 9dcd946..8eef930 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -15,8 +15,6 @@
  */
 package com.android.systemui;
 
-import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
-
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
@@ -99,8 +97,22 @@
             .setProvideMainThread(true)
             .build();
 
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+    @ClassRule
+    public static final SetFlagsRule.ClassRule mSetFlagsClassRule =
+            new SetFlagsRule.ClassRule(
+                    android.app.Flags.class,
+                    android.hardware.biometrics.Flags.class,
+                    android.multiuser.Flags.class,
+                    android.net.platform.flags.Flags.class,
+                    android.os.Flags.class,
+                    android.service.controls.flags.Flags.class,
+                    com.android.internal.telephony.flags.Flags.class,
+                    com.android.server.notification.Flags.class,
+                    com.android.systemui.Flags.class);
+
+    // TODO(b/339471826): Fix Robolectric to execute the @ClassRule correctly
+    @Rule public final SetFlagsRule mSetFlagsRule =
+            isRobolectricTest() ? new SetFlagsRule() : mSetFlagsClassRule.createSetFlagsRule();
 
     @Rule(order = 10)
     public final SceneContainerRule mSceneContainerRule = new SceneContainerRule();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt
index 4085b1b..923b636 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt
@@ -25,8 +25,9 @@
 @SysUISingleton
 class FakeAccessibilityRepository(
     override val isTouchExplorationEnabled: MutableStateFlow<Boolean>,
+    override val isEnabled: MutableStateFlow<Boolean>,
 ) : AccessibilityRepository {
-    @Inject constructor() : this(MutableStateFlow(false))
+    @Inject constructor() : this(MutableStateFlow(false), MutableStateFlow(false))
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeOneHandedModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeOneHandedModeRepository.kt
new file mode 100644
index 0000000..ac135af
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeOneHandedModeRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.data.repository
+
+import android.os.UserHandle
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class FakeOneHandedModeRepository : OneHandedModeRepository {
+    private val userMap = mutableMapOf<Int, MutableStateFlow<Boolean>>()
+
+    override fun isEnabled(userHandle: UserHandle): StateFlow<Boolean> {
+        return getFlow(userHandle.identifier)
+    }
+
+    override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean {
+        getFlow(userHandle.identifier).value = isEnabled
+        return true
+    }
+
+    /** initializes the flow if already not */
+    private fun getFlow(userId: Int): MutableStateFlow<Boolean> {
+        return userMap.getOrPut(userId) { MutableStateFlow(false) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryKosmos.kt
index bf22563..9ee200a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryKosmos.kt
@@ -14,12 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.accessibility.data.repository
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.fakeOneHandedModeRepository by Kosmos.Fixture { FakeOneHandedModeRepository() }
+val Kosmos.oneHandedModeRepository by Kosmos.Fixture { fakeOneHandedModeRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
index 66c9afb..b23767e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
@@ -17,5 +17,13 @@
 package com.android.systemui.animation
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
 
-val Kosmos.activityTransitionAnimator by Kosmos.Fixture { ActivityTransitionAnimator() }
+val Kosmos.activityTransitionAnimator by
+    Kosmos.Fixture {
+        ActivityTransitionAnimator(
+            // The main thread is checked in a bunch of places inside the different transitions
+            // animators, so we have to pass the real main executor here.
+            mainExecutor = testCase.context.mainExecutor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt
index 77cb167..5a092f3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt
@@ -19,7 +19,13 @@
 import com.android.systemui.jank.interactionJankMonitor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testCase
 
 val Kosmos.dialogTransitionAnimator by Fixture {
-    fakeDialogTransitionAnimator(interactionJankMonitor = interactionJankMonitor)
+    fakeDialogTransitionAnimator(
+        // The main thread is checked in a bunch of places inside the different transitions
+        // animators, so we have to pass the real main executor here.
+        mainExecutor = testCase.context.mainExecutor,
+        interactionJankMonitor = interactionJankMonitor,
+    )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt
index 48b72d0..1709329 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt
@@ -15,17 +15,20 @@
 package com.android.systemui.animation
 
 import com.android.internal.jank.InteractionJankMonitor
-import com.android.systemui.jank.interactionJankMonitor
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
 
 /** A [DialogTransitionAnimator] to be used in tests. */
 @JvmOverloads
 fun fakeDialogTransitionAnimator(
+    @Main mainExecutor: Executor,
     isUnlocked: Boolean = true,
     isShowingAlternateAuthOnUnlock: Boolean = false,
     isPredictiveBackQsDialogAnim: Boolean = false,
     interactionJankMonitor: InteractionJankMonitor,
 ): DialogTransitionAnimator {
     return DialogTransitionAnimator(
+        mainExecutor = mainExecutor,
         callback =
             FakeCallback(
                 isUnlocked = isUnlocked,
@@ -36,7 +39,7 @@
             object : AnimationFeatureFlags {
                 override val isPredictiveBackQsDialogAnim = isPredictiveBackQsDialogAnim
             },
-        transitionAnimator = fakeTransitionAnimator(),
+        transitionAnimator = fakeTransitionAnimator(mainExecutor),
         isForTesting = true,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt
index bc7ec3f..d07875f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt
@@ -15,10 +15,12 @@
 package com.android.systemui.animation
 
 import com.android.app.animation.Interpolators
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
 
 /** A [TransitionAnimator] to be used in tests. */
-fun fakeTransitionAnimator(): TransitionAnimator {
-    return TransitionAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
+fun fakeTransitionAnimator(@Main mainExecutor: Executor): TransitionAnimator {
+    return TransitionAnimator(mainExecutor, TEST_TIMINGS, TEST_INTERPOLATORS)
 }
 
 /**
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
index 2e2cf9a..e37bdc1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -1,8 +1,6 @@
 package com.android.systemui.biometrics.data.repository
 
-import android.hardware.biometrics.Flags
 import android.hardware.biometrics.PromptInfo
-import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.shared.model.PromptKind
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -22,15 +20,12 @@
     private var _challenge = MutableStateFlow<Long?>(null)
     override val challenge = _challenge.asStateFlow()
 
-    private val _kind = MutableStateFlow<PromptKind>(PromptKind.Biometric())
-    override val kind = _kind.asStateFlow()
+    private val _promptKind = MutableStateFlow<PromptKind>(PromptKind.None)
+    override val promptKind = _promptKind.asStateFlow()
 
     private val _isConfirmationRequired = MutableStateFlow(false)
     override val isConfirmationRequired = _isConfirmationRequired.asStateFlow()
 
-    private val _showBpWithoutIconForCredential = MutableStateFlow(false)
-    override val showBpWithoutIconForCredential = _showBpWithoutIconForCredential.asStateFlow()
-
     private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null)
     override val opPackageName = _opPackageName.asStateFlow()
 
@@ -61,7 +56,7 @@
         _promptInfo.value = promptInfo
         _userId.value = userId
         _challenge.value = gatekeeperChallenge
-        _kind.value = kind
+        _promptKind.value = kind
         _isConfirmationRequired.value = promptInfo.isConfirmationRequested || forceConfirmation
         _opPackageName.value = opPackageName
     }
@@ -70,21 +65,11 @@
         _promptInfo.value = null
         _userId.value = null
         _challenge.value = null
-        _kind.value = PromptKind.Biometric()
+        _promptKind.value = PromptKind.None
+        _opPackageName.value = null
         _isConfirmationRequired.value = false
     }
 
-    override fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) {
-        val hasCredentialViewShown = kind.value !is PromptKind.Biometric
-        val showBpForCredential =
-            Flags.customBiometricPrompt() &&
-                com.android.systemui.Flags.constraintBp() &&
-                !Utils.isBiometricAllowed(promptInfo) &&
-                Utils.isDeviceCredentialAllowed(promptInfo) &&
-                promptInfo.contentView != null
-        _showBpWithoutIconForCredential.value = showBpForCredential && !hasCredentialViewShown
-    }
-
     fun setIsShowing(showing: Boolean) {
         _isShowing.value = showing
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt
index 2ae6f542..4999a5a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.biometrics.ui.viewmodel
 
 import android.content.applicationContext
+import com.android.app.activityTaskManager
+import com.android.launcher3.icons.IconProvider
 import com.android.systemui.biometrics.domain.interactor.biometricStatusInteractor
 import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.promptSelectorInteractor
@@ -32,6 +34,8 @@
         context = applicationContext,
         udfpsOverlayInteractor = udfpsOverlayInteractor,
         biometricStatusInteractor = biometricStatusInteractor,
-        udfpsUtils = udfpsUtils
+        udfpsUtils = udfpsUtils,
+        iconProvider = IconProvider(applicationContext),
+        activityTaskManager = activityTaskManager,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt
index d3ceb15..f5d02f3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt
@@ -38,4 +38,8 @@
                 )
             )
     }
+
+    fun setBaseUserRestriction() {
+        _restrictionPolicy.value = PolicyRestriction.Restricted(RestrictedLockUtils.EnforcedAdmin())
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt
index 3a61bf6..9b3482b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt
@@ -18,8 +18,12 @@
 
 import android.os.UserHandle
 import com.android.systemui.common.shared.model.PackageChangeModel
+import com.android.systemui.common.shared.model.PackageInstallSession
 import com.android.systemui.util.time.SystemClock
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.filter
 
 class FakePackageChangeRepository(private val systemClock: SystemClock) : PackageChangeRepository {
@@ -31,6 +35,15 @@
             user == UserHandle.ALL || user == UserHandle.getUserHandleForUid(it.packageUid)
         }
 
+    private val _packageInstallSessions = MutableStateFlow<List<PackageInstallSession>>(emptyList())
+
+    override val packageInstallSessionsForPrimaryUser: Flow<List<PackageInstallSession>> =
+        _packageInstallSessions.asStateFlow()
+
+    fun setInstallSessions(sessions: List<PackageInstallSession>) {
+        _packageInstallSessions.value = sessions
+    }
+
     suspend fun notifyChange(model: PackageChangeModel) {
         _packageChanged.emit(model)
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
index 9f5c6b8..d958bae 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
@@ -23,6 +23,7 @@
 ) : CommunalRepository {
     override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) {
         this.currentScene.value = toScene
+        this._transitionState.value = flowOf(ObservableTransitionState.Idle(toScene))
     }
 
     private val defaultTransitionState = ObservableTransitionState.Idle(CommunalScenes.Default)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 329c0f1..f7ce367 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -51,6 +51,7 @@
     override fun abortRestoreWidgets() {}
 
     private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, priority: Int) {
-        _communalWidgets.value += listOf(CommunalWidgetContentModel(id, providerInfo, priority))
+        _communalWidgets.value +=
+            listOf(CommunalWidgetContentModel.Available(id, providerInfo, priority))
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/FakeShortcutHelperStartActivity.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/FakeShortcutHelperStartActivity.kt
index bf22563..3190171 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/FakeShortcutHelperStartActivity.kt
@@ -14,12 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.keyboard.shortcut
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import android.content.Intent
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
+class FakeShortcutHelperStartActivity : (Intent) -> Unit {
+
+    val startIntents = mutableListOf<Intent>()
+
+    override fun invoke(intent: Intent) {
+        startIntents += intent
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
new file mode 100644
index 0000000..3401cc4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.keyboard.shortcut
+
+import android.content.applicationContext
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperRepository
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperTestHelper
+import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperInteractor
+import com.android.systemui.keyboard.shortcut.ui.ShortcutHelperActivityStarter
+import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel
+import com.android.systemui.keyguard.data.repository.fakeCommandQueue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.model.sysUiState
+import com.android.systemui.settings.displayTracker
+
+val Kosmos.shortcutHelperRepository by
+    Kosmos.Fixture { ShortcutHelperRepository(fakeCommandQueue, broadcastDispatcher) }
+
+val Kosmos.shortcutHelperTestHelper by
+    Kosmos.Fixture {
+        ShortcutHelperTestHelper(
+            shortcutHelperRepository,
+            applicationContext,
+            broadcastDispatcher,
+            fakeCommandQueue
+        )
+    }
+
+val Kosmos.shortcutHelperInteractor by
+    Kosmos.Fixture {
+        ShortcutHelperInteractor(displayTracker, testScope, sysUiState, shortcutHelperRepository)
+    }
+
+val Kosmos.shortcutHelperViewModel by
+    Kosmos.Fixture { ShortcutHelperViewModel(testDispatcher, shortcutHelperInteractor) }
+
+val Kosmos.fakeShortcutHelperStartActivity by Kosmos.Fixture { FakeShortcutHelperStartActivity() }
+
+val Kosmos.shortcutHelperActivityStarter by
+    Kosmos.Fixture {
+        ShortcutHelperActivityStarter(
+            applicationContext,
+            applicationCoroutineScope,
+            shortcutHelperViewModel,
+            fakeShortcutHelperStartActivity,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
new file mode 100644
index 0000000..772ce41
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.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.keyboard.shortcut.data.repository
+
+import android.content.Context
+import android.content.Intent
+import com.android.systemui.broadcast.FakeBroadcastDispatcher
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+
+class ShortcutHelperTestHelper(
+    repo: ShortcutHelperRepository,
+    private val context: Context,
+    private val fakeBroadcastDispatcher: FakeBroadcastDispatcher,
+    private val fakeCommandQueue: FakeCommandQueue,
+) {
+
+    init {
+        repo.start()
+    }
+
+    fun hideFromActivity() {
+        fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+            context,
+            Intent(Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS)
+        )
+    }
+
+    fun showFromActivity() {
+        fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+            context,
+            Intent(Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS)
+        )
+    }
+
+    fun toggle(deviceId: Int) {
+        fakeCommandQueue.doForEachCallback { it.toggleKeyboardShortcutsMenu(deviceId) }
+    }
+
+    fun hideForSystem() {
+        fakeCommandQueue.doForEachCallback { it.dismissKeyboardShortcutsMenu() }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index a242368..2fe7438 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -40,12 +40,21 @@
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 
-/** Fake implementation of [KeyguardTransitionRepository] */
+/**
+ * Fake implementation of [KeyguardTransitionRepository].
+ *
+ * By default, will be seeded with a transition from OFF -> LOCKSCREEN, which is the most common
+ * case. If the lockscreen is disabled, or we're in setup wizard, the repository will initialize
+ * with OFF -> GONE. Construct with initInLockscreen = false if your test requires this behavior.
+ */
 @SysUISingleton
-class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitionRepository {
+class FakeKeyguardTransitionRepository(
+    private val initInLockscreen: Boolean = true,
+) : KeyguardTransitionRepository {
     private val _transitions =
         MutableSharedFlow<TransitionStep>(replay = 3, onBufferOverflow = BufferOverflow.DROP_OLDEST)
     override val transitions: SharedFlow<TransitionStep> = _transitions
+    @Inject constructor() : this(initInLockscreen = true)
 
     private val _currentTransitionInfo: MutableStateFlow<TransitionInfo> =
         MutableStateFlow(
@@ -59,8 +68,21 @@
     override var currentTransitionInfoInternal = _currentTransitionInfo.asStateFlow()
 
     init {
-        // Seed the fake repository with the same initial steps the actual repository uses.
-        KeyguardTransitionRepositoryImpl.initialTransitionSteps.forEach { _transitions.tryEmit(it) }
+        // Seed with a FINISHED transition in OFF, same as the real repository.
+        _transitions.tryEmit(
+            TransitionStep(
+                KeyguardState.OFF,
+                KeyguardState.OFF,
+                1f,
+                TransitionState.FINISHED,
+            )
+        )
+
+        if (initInLockscreen) {
+            tryEmitInitialStepsFromOff(KeyguardState.LOCKSCREEN)
+        } else {
+            tryEmitInitialStepsFromOff(KeyguardState.OFF)
+        }
     }
 
     /**
@@ -223,6 +245,32 @@
         return if (info.animator == null) UUID.randomUUID() else null
     }
 
+    override suspend fun emitInitialStepsFromOff(to: KeyguardState) {
+        tryEmitInitialStepsFromOff(to)
+    }
+
+    private fun tryEmitInitialStepsFromOff(to: KeyguardState) {
+        _transitions.tryEmit(
+            TransitionStep(
+                KeyguardState.OFF,
+                to,
+                0f,
+                TransitionState.STARTED,
+                ownerName = "KeyguardTransitionRepository(boot)",
+            )
+        )
+
+        _transitions.tryEmit(
+            TransitionStep(
+                KeyguardState.OFF,
+                to,
+                1f,
+                TransitionState.FINISHED,
+                ownerName = "KeyguardTransitionRepository(boot)",
+            ),
+        )
+    }
+
     override fun updateTransition(
         transitionId: UUID,
         @FloatRange(from = 0.0, to = 1.0) value: Float,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
index 90a93f4..a6b40df 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
 import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
 import com.android.systemui.keyguard.ui.viewmodel.keyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
 import com.android.systemui.keyguard.ui.viewmodel.keyguardSmartspaceViewModel
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.util.mockito.mock
@@ -37,6 +38,7 @@
             context = applicationContext,
             smartspaceViewModel = keyguardSmartspaceViewModel,
             blueprintInteractor = { keyguardBlueprintInteractor },
+            rootViewModel = keyguardRootViewModel,
         )
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
index 408157b..3e69e87 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
@@ -17,7 +17,10 @@
 package com.android.systemui.keyguard.data.repository
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
 
 var Kosmos.keyguardTransitionRepository: KeyguardTransitionRepository by
     Kosmos.Fixture { fakeKeyguardTransitionRepository }
 var Kosmos.fakeKeyguardTransitionRepository by Kosmos.Fixture { FakeKeyguardTransitionRepository() }
+var Kosmos.realKeyguardTransitionRepository: KeyguardTransitionRepository by
+    Kosmos.Fixture { KeyguardTransitionRepositoryImpl(testDispatcher) }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt
index bf22563..7d0e8b1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt
@@ -14,12 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.keyguard.data.repository
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.lockscreenSceneTransitionRepository by
+    Kosmos.Fixture { LockscreenSceneTransitionRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
index 2c6d44f..03e5a90 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 @ExperimentalCoroutinesApi
@@ -29,5 +30,6 @@
             transitionInteractor = keyguardTransitionInteractor,
             dismissInteractor = keyguardDismissInteractor,
             applicationScope = testScope.backgroundScope,
+            sceneInteractor = sceneInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractorKosmos.kt
new file mode 100644
index 0000000..7d8d33f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractorKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.domain.interactor
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
+
+val Kosmos.keyguardTransitionBootInteractor: KeyguardTransitionBootInteractor by
+    Kosmos.Fixture {
+        KeyguardTransitionBootInteractor(
+            scope = applicationCoroutineScope,
+            deviceEntryInteractor = deviceEntryInteractor,
+            deviceProvisioningInteractor = deviceProvisioningInteractor,
+            keyguardTransitionInteractor = keyguardTransitionInteractor,
+            repository = keyguardTransitionRepository,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index 6cc1e8e..c90642d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 
 val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by
     Kosmos.Fixture {
@@ -32,5 +33,6 @@
             fromAodTransitionInteractor = { fromAodTransitionInteractor },
             fromAlternateBouncerTransitionInteractor = { fromAlternateBouncerTransitionInteractor },
             fromDozingTransitionInteractor = { fromDozingTransitionInteractor },
+            sceneInteractor = { sceneInteractor }
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
index 29167d6..b38acc8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor
 
 val Kosmos.windowManagerLockscreenVisibilityInteractor by
@@ -29,5 +30,6 @@
             fromBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
             fromAlternateBouncerInteractor = fromAlternateBouncerTransitionInteractor,
             notificationLaunchAnimationInteractor = notificationLaunchAnimationInteractor,
+            sceneInteractor = sceneInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
new file mode 100644
index 0000000..3c1f7b1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.domain.interactor.scenetransition
+
+import com.android.systemui.keyguard.data.repository.lockscreenSceneTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+
+var Kosmos.lockscreenSceneTransitionInteractor by
+    Kosmos.Fixture {
+        LockscreenSceneTransitionInteractor(
+            transitionInteractor = keyguardTransitionInteractor,
+            applicationScope = applicationCoroutineScope,
+            sceneInteractor = sceneInteractor,
+            repository = lockscreenSceneTransitionRepository,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
index 460913f..b8fcec6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
@@ -18,7 +18,6 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +26,6 @@
 
 val Kosmos.aodToLockscreenTransitionViewModel by Fixture {
     AodToLockscreenTransitionViewModel(
-        deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
         shadeInteractor = shadeInteractor,
         animationFlow = keyguardTransitionAnimationFlow,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
index 58b0ff8..67fa857 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import com.android.systemui.accessibility.domain.interactor.accessibilityInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntrySourceInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
@@ -49,6 +50,7 @@
         keyguardViewController = { statusBarKeyguardViewManager },
         deviceEntryInteractor = deviceEntryInteractor,
         deviceEntrySourceInteractor = deviceEntrySourceInteractor,
+        accessibilityInteractor = accessibilityInteractor,
         scope = testScope.backgroundScope,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index 3762497..ec56327 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.jank.interactionJankMonitor
 import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -33,6 +34,7 @@
             uiEventLogger,
             { interactionJankMonitor },
             mock(),
+            { keyguardTransitionInteractor },
             { shadeInteractor },
             { deviceUnlockedInteractor },
             { sceneInteractor },
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt
index bf22563..8ad6087 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt
@@ -14,12 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qrcodescanner
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.util.mockito.mock
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.qrCodeScannerController by Kosmos.Fixture { mock<QRCodeScannerController>() }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt
similarity index 97%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt
index 302ac35..093ebd6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.pipeline.domain.interactor
+package com.android.systemui.qs
 
 import com.android.internal.logging.InstanceId
 import com.android.systemui.animation.Expandable
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
index 3f91122..d72630d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.plugins.qs.QSFactory
+import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl
 import com.android.systemui.qs.footer.foregroundServicesRepository
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -48,7 +49,7 @@
     QsEventLoggerFake(uiEventLoggerFake, instanceIdSequenceFake)
 }
 
-var Kosmos.qsTileFactory by Fixture<QSFactory>()
+var Kosmos.qsTileFactory by Fixture<QSFactory> { FakeQSFactory(::tileCreator) }
 
 val Kosmos.fgsManagerController by Fixture { FakeFgsManagerController() }
 
@@ -98,3 +99,7 @@
         showPowerButton = true,
     )
 }
+
+private fun tileCreator(spec: String): QSTile {
+    return FakeQSTile(0).apply { tileSpec = spec }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt
index cff5980..744942c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt
@@ -16,11 +16,11 @@
 
 package com.android.systemui.qs.external
 
-import android.os.Binder
 import android.os.IBinder
+import android.os.IInterface
 import android.service.quicksettings.IQSTileService
 
-class FakeIQSTileService : IQSTileService {
+class FakeIQSTileService : IQSTileService.Stub() {
 
     var isTileAdded: Boolean = false
         private set
@@ -31,9 +31,11 @@
         get() = mutableClicks
 
     private val mutableClicks: MutableList<IBinder?> = mutableListOf()
-    private val binder = Binder()
+    override fun queryLocalInterface(descriptor: String): IInterface {
+        return this
+    }
 
-    override fun asBinder(): IBinder = binder
+    override fun asBinder(): IBinder = this
 
     override fun onTileAdded() {
         isTileAdded = true
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt
new file mode 100644
index 0000000..a0fc76b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.qs.external
+
+import android.app.activityManager
+import android.content.applicationContext
+import android.os.fakeExecutorHandler
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by
+    Kosmos.Fixture {
+        TileLifecycleManager.Factory { intent, userHandle ->
+            TileLifecycleManager(
+                fakeExecutorHandler,
+                applicationContext,
+                tileServices,
+                packageManagerAdapterFacade.packageManagerAdapter,
+                broadcastDispatcher,
+                intent,
+                userHandle,
+                activityManager,
+                mock(),
+                fakeExecutor,
+            )
+        }
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServicesKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServicesKosmos.kt
new file mode 100644
index 0000000..3f129da
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServicesKosmos.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.qs.external
+
+import android.content.applicationContext
+import android.os.fakeExecutorHandler
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository
+import com.android.systemui.qs.pipeline.domain.interactor.panelInteractor
+import com.android.systemui.settings.userTracker
+import com.android.systemui.statusbar.commandQueue
+import com.android.systemui.statusbar.phone.ui.StatusBarIconController
+import com.android.systemui.statusbar.policy.keyguardStateController
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+val Kosmos.tileServices: TileServices by
+    Kosmos.Fixture {
+        val qsHost: QSHost = mock { whenever(context).thenReturn(applicationContext) }
+        TileServices(
+            qsHost,
+            { fakeExecutorHandler },
+            broadcastDispatcher,
+            userTracker,
+            keyguardStateController,
+            commandQueue,
+            mock<StatusBarIconController>(),
+            panelInteractor,
+            tileLifecycleManagerFactory,
+            customTileAddedRepository,
+            fakeExecutor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt
index 36c2c2b..9a6730e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt
@@ -18,13 +18,9 @@
 
 import android.content.ComponentName
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
 
 var Kosmos.componentName: ComponentName by Kosmos.Fixture()
 
-/** Returns mocks */
-var Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by Kosmos.Fixture { mock {} }
-
 val Kosmos.iQSTileService: FakeIQSTileService by Kosmos.Fixture { FakeIQSTileService() }
 val Kosmos.tileServiceManagerFacade: FakeTileServiceManagerFacade by
     Kosmos.Fixture { FakeTileServiceManagerFacade(iQSTileService) }
@@ -34,4 +30,3 @@
 
 val Kosmos.tileServicesFacade: FakeTileServicesFacade by
     Kosmos.Fixture { (FakeTileServicesFacade(tileServiceManager)) }
-val Kosmos.tileServices: TileServices by Kosmos.Fixture { tileServicesFacade.tileServices }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt
index f846d57..4acedaa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt
@@ -18,4 +18,5 @@
 
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.gridLayoutTypeRepository by Kosmos.Fixture { GridLayoutTypeRepository() }
+var Kosmos.gridLayoutTypeRepository: GridLayoutTypeRepository by
+    Kosmos.Fixture { GridLayoutTypeRepositoryImpl() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepositoryKosmos.kt
new file mode 100644
index 0000000..d686699
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepositoryKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.qs.panels.data.repository
+
+import android.content.packageManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.qs.pipeline.data.repository.installedTilesRepository
+import com.android.systemui.settings.userTracker
+import com.android.systemui.util.mockito.whenever
+
+val Kosmos.iconAndNameCustomRepository by
+    Kosmos.Fixture {
+        whenever(userTracker.userContext.packageManager).thenReturn(packageManager)
+        IconAndNameCustomRepository(
+            installedTilesRepository,
+            userTracker,
+            backgroundCoroutineContext,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
index 685e772..e40152a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
@@ -18,4 +18,4 @@
 
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.iconTilesRepository by Kosmos.Fixture { IconTilesRepositoryImpl() }
+var Kosmos.iconTilesRepository: IconTilesRepository by Kosmos.Fixture { IconTilesRepositoryImpl() }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepositoryKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepositoryKosmos.kt
index bf22563..d8af3fa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepositoryKosmos.kt
@@ -14,12 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.data.repository
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.infiniteGridSizeRepository by Kosmos.Fixture { InfiniteGridSizeRepository() }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/StockTilesRepositoryKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/StockTilesRepositoryKosmos.kt
index bf22563..ff33650 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/StockTilesRepositoryKosmos.kt
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.data.repository
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+var Kosmos.stockTilesRepository by
+    Kosmos.Fixture {
+        testCase.context.orCreateTestableResources
+        StockTilesRepository(testCase.context.resources)
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractorKosmos.kt
new file mode 100644
index 0000000..bd54fd4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractorKosmos.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.qs.panels.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.data.repository.iconAndNameCustomRepository
+import com.android.systemui.qs.panels.data.repository.stockTilesRepository
+import com.android.systemui.qs.tiles.viewmodel.qSTileConfigProvider
+
+val Kosmos.editTilesListInteractor by
+    Kosmos.Fixture {
+        EditTilesListInteractor(
+            stockTilesRepository,
+            qSTileConfigProvider,
+            iconAndNameCustomRepository,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt
new file mode 100644
index 0000000..edbc4c1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.qs.panels.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+
+val Kosmos.gridConsistencyInteractor by
+    Kosmos.Fixture {
+        GridConsistencyInteractor(
+            gridLayoutTypeInteractor,
+            currentTilesInteractor,
+            gridConsistencyInteractorsMap,
+            noopGridConsistencyInteractor,
+            FakeLogBuffer.Factory.create(),
+            applicationCoroutineScope,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
index c951642..34e99d3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
@@ -27,3 +27,6 @@
 
 val Kosmos.gridLayoutMap: Map<GridLayoutType, GridLayout> by
     Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridLayout)) }
+
+var Kosmos.gridConsistencyInteractorsMap: Map<GridLayoutType, GridTypeConsistencyInteractor> by
+    Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)) }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt
index bf22563..7f387d7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.domain.interactor
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.infiniteGridConsistencyInteractor by
+    Kosmos.Fixture {
+        InfiniteGridConsistencyInteractor(iconTilesInteractor, infiniteGridSizeInteractor)
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
index 1893c30..34b266a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
@@ -19,4 +19,5 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
 
-val Kosmos.infiniteGridLayout by Kosmos.Fixture { InfiniteGridLayout(iconTilesInteractor) }
+val Kosmos.infiniteGridLayout by
+    Kosmos.Fixture { InfiniteGridLayout(iconTilesInteractor, infiniteGridSizeInteractor) }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt
index bf22563..6e11977 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt
@@ -14,12 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.domain.interactor
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.data.repository.infiniteGridSizeRepository
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.infiniteGridSizeInteractor by
+    Kosmos.Fixture { InfiniteGridSizeInteractor(infiniteGridSizeRepository) }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt
index bf22563..e3beff7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt
@@ -14,12 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.domain.interactor
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.noopGridConsistencyInteractor by Kosmos.Fixture { NoopGridConsistencyInteractor() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt
new file mode 100644
index 0000000..612a5d9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.qs.panels.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.qs.panels.domain.interactor.editTilesListInteractor
+import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap
+import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor
+import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+import com.android.systemui.qs.pipeline.domain.interactor.minimumTilesInteractor
+
+val Kosmos.editModeViewModel by
+    Kosmos.Fixture {
+        EditModeViewModel(
+            editTilesListInteractor,
+            currentTilesInteractor,
+            minimumTilesInteractor,
+            infiniteGridLayout,
+            applicationCoroutineScope,
+            gridLayoutTypeInteractor,
+            gridLayoutMap,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
index 5fd8762..9481fca 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
@@ -20,8 +20,7 @@
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap
 import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor
-import com.android.systemui.qs.panels.domain.interactor.iconTilesInteractor
-import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
+import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout
 import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
 
 val Kosmos.tileGridViewModel by
@@ -30,7 +29,7 @@
             gridLayoutTypeInteractor,
             gridLayoutMap,
             currentTilesInteractor,
-            InfiniteGridLayout(iconTilesInteractor),
+            infiniteGridLayout,
             applicationCoroutineScope,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt
index ff6b7d0..ed4c67e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt
@@ -17,23 +17,78 @@
 package com.android.systemui.qs.pipeline.data.repository
 
 import android.content.ComponentName
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.ServiceInfo
+import android.graphics.drawable.Drawable
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
 
 class FakeInstalledTilesComponentRepository : InstalledTilesComponentRepository {
 
-    private val installedComponentsPerUser =
-        mutableMapOf<Int, MutableStateFlow<Set<ComponentName>>>()
+    private val installedServicesPerUser = mutableMapOf<Int, MutableStateFlow<List<ServiceInfo>>>()
 
     override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> {
-        return getFlow(userId).asStateFlow()
+        return getFlow(userId).map { it.map { it.componentName }.toSet() }
+    }
+
+    override fun getInstalledTilesServiceInfos(userId: Int): List<ServiceInfo> {
+        return getFlow(userId).value
     }
 
     fun setInstalledPackagesForUser(userId: Int, components: Set<ComponentName>) {
-        getFlow(userId).value = components
+        getFlow(userId).value =
+            components.map {
+                ServiceInfo().apply {
+                    packageName = it.packageName
+                    name = it.className
+                    applicationInfo = ApplicationInfo()
+                }
+            }
     }
 
-    private fun getFlow(userId: Int): MutableStateFlow<Set<ComponentName>> =
-        installedComponentsPerUser.getOrPut(userId) { MutableStateFlow(emptySet()) }
+    fun setInstalledServicesForUser(userId: Int, services: List<ServiceInfo>) {
+        getFlow(userId).value = services.toList()
+    }
+
+    private fun getFlow(userId: Int): MutableStateFlow<List<ServiceInfo>> =
+        installedServicesPerUser.getOrPut(userId) { MutableStateFlow(emptyList()) }
+
+    companion object {
+        fun ServiceInfo(
+            componentName: ComponentName,
+            serviceName: String,
+            serviceIcon: Drawable? = null,
+            appName: String = "",
+            appIcon: Drawable? = null
+        ): ServiceInfo {
+            val appInfo =
+                object : ApplicationInfo() {
+                    override fun loadLabel(pm: PackageManager): CharSequence {
+                        return appName
+                    }
+
+                    override fun loadIcon(pm: PackageManager?): Drawable? {
+                        return appIcon
+                    }
+                }
+            val serviceInfo =
+                object : ServiceInfo() {
+                        override fun loadLabel(pm: PackageManager): CharSequence {
+                            return serviceName
+                        }
+
+                        override fun loadIcon(pm: PackageManager?): Drawable? {
+                            return serviceIcon ?: getApplicationInfo().loadIcon(pm)
+                        }
+                    }
+                    .apply {
+                        packageName = componentName.packageName
+                        name = componentName.className
+                        applicationInfo = appInfo
+                    }
+            return serviceInfo
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/MinimumTilesInteractorKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/MinimumTilesInteractorKosmos.kt
index bf22563..ef1189f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/MinimumTilesInteractorKosmos.kt
@@ -14,12 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.pipeline.domain.interactor
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.pipeline.data.repository.minimumTilesRepository
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+var Kosmos.minimumTilesInteractor by
+    Kosmos.Fixture { MinimumTilesInteractor(minimumTilesRepository) }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorKosmos.kt
index bf22563..d10780b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorKosmos.kt
@@ -14,12 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.pipeline.domain.interactor
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.shade.shadeController
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.panelInteractor by Kosmos.Fixture { PanelInteractorImpl(shadeController) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
index c4bf8ff..f50443e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
@@ -31,7 +31,11 @@
 
     private val mutableInputs = mutableListOf<Input>()
 
-    override fun handle(expandable: Expandable?, intent: Intent) {
+    override fun handle(
+        expandable: Expandable?,
+        intent: Intent,
+        handleDismissShadeShowOverLockScreenWhenLocked: Boolean
+    ) {
         mutableInputs.add(Input.Intent(expandable, intent))
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt
index bf22563..ccfb609 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt
@@ -14,12 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.tiles.base.actions
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.qsTileIntentUserInputHandler by Kosmos.Fixture { FakeQSTileIntentUserInputHandler() }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt
index bf22563..146c1ad 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt
@@ -14,12 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.tiles.base.analytics
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.qsTileAnalytics by Kosmos.Fixture { mock<QSTileAnalytics>() }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt
index bf22563..9ad49f0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt
@@ -14,12 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.tiles.base.interactor
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.fakeDisabledByPolicyInteractor by Kosmos.Fixture { FakeDisabledByPolicyInteractor() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
index 5c4b390..419e781 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
@@ -16,23 +16,63 @@
 
 package com.android.systemui.qs.tiles.di
 
+import android.os.UserHandle
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.instanceIdSequenceFake
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
 import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
 import com.android.systemui.qs.tiles.viewmodel.qSTileConfigProvider
 import com.android.systemui.qs.tiles.viewmodel.qsTileViewModelAdaperFactory
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import javax.inject.Provider
-import org.mockito.Mockito
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.StateFlow
 
 var Kosmos.newFactoryTileMap by Kosmos.Fixture { emptyMap<String, Provider<QSTileViewModel>>() }
 
+val Kosmos.customTileViewModelFactory: QSTileViewModelFactory.Component by
+    Kosmos.Fixture {
+        mock {
+            whenever(create(any())).thenAnswer { invocation ->
+                val tileSpec = invocation.getArgument<TileSpec>(0)
+                val config =
+                    QSTileConfig(
+                        tileSpec,
+                        QSTileUIConfig.Empty,
+                        instanceIdSequenceFake.newInstanceId(),
+                    )
+                object : QSTileViewModel {
+                    override val state: SharedFlow<QSTileState> =
+                        MutableStateFlow(QSTileState.build({ null }, tileSpec.spec) {})
+                    override val config: QSTileConfig = config
+                    override val isAvailable: StateFlow<Boolean> = MutableStateFlow(true)
+
+                    override fun onUserChanged(user: UserHandle) {}
+
+                    override fun forceUpdate() {}
+
+                    override fun onActionPerformed(userAction: QSTileUserAction) {}
+
+                    override fun destroy() {}
+                }
+            }
+        }
+    }
+
 val Kosmos.newQSTileFactory by
     Kosmos.Fixture {
         NewQSTileFactory(
             qSTileConfigProvider,
             qsTileViewModelAdaperFactory,
             newFactoryTileMap,
-            mock(Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)),
-            mock(Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)),
+            customTileViewModelFactory,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
index 561e254..42437d5a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.qs.external.FakeCustomTileStatePersister
 import com.android.systemui.qs.external.tileServices
+import com.android.systemui.qs.external.tileServicesFacade
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
 import com.android.systemui.qs.tiles.base.logging.QSTileLogger
@@ -38,10 +39,10 @@
 import com.android.systemui.user.data.repository.userRepository
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.tileSpec: TileSpec.CustomTileSpec by Kosmos.Fixture()
+var Kosmos.customTileSpec: TileSpec.CustomTileSpec by Kosmos.Fixture()
 
 var Kosmos.customTileQsTileConfig: QSTileConfig by
-    Kosmos.Fixture { QSTileConfigTestBuilder.build { tileSpec = this@Fixture.tileSpec } }
+    Kosmos.Fixture { QSTileConfigTestBuilder.build { tileSpec = this@Fixture.customTileSpec } }
 val Kosmos.qsTileLogger: QSTileLogger by Kosmos.Fixture { mock {} }
 
 val Kosmos.customTileStatePersister: FakeCustomTileStatePersister by
@@ -50,7 +51,7 @@
 val Kosmos.customTileInteractor: CustomTileInteractor by
     Kosmos.Fixture {
         CustomTileInteractor(
-            tileSpec,
+            customTileSpec,
             customTileDefaultsRepository,
             customTileRepository,
             testScope.backgroundScope,
@@ -61,7 +62,7 @@
 val Kosmos.customTileRepository: FakeCustomTileRepository by
     Kosmos.Fixture {
         FakeCustomTileRepository(
-            tileSpec,
+            customTileSpec,
             customTileStatePersister,
             packageManagerAdapterFacade,
             testScope.testScheduler,
@@ -75,18 +76,18 @@
     Kosmos.Fixture { FakeCustomTilePackageUpdatesRepository() }
 
 val Kosmos.packageManagerAdapterFacade: FakePackageManagerAdapterFacade by
-    Kosmos.Fixture { FakePackageManagerAdapterFacade(tileSpec.componentName) }
+    Kosmos.Fixture { FakePackageManagerAdapterFacade(customTileSpec.componentName) }
 
 val Kosmos.customTileServiceInteractor: CustomTileServiceInteractor by
     Kosmos.Fixture {
         CustomTileServiceInteractor(
-            tileSpec,
+            customTileSpec,
             activityStarter,
             { customTileUserActionInteractor },
             customTileInteractor,
             userRepository,
             qsTileLogger,
-            tileServices,
+            tileServicesFacade.tileServices,
             testScope.backgroundScope,
         )
     }
@@ -95,7 +96,7 @@
     Kosmos.Fixture {
         CustomTileUserActionInteractor(
             testCase.context,
-            tileSpec,
+            customTileSpec,
             qsTileLogger,
             mock {},
             mock {},
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
index 634d121..fa8d363 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.custom.data.repository
 
 import android.content.ComponentName
+import android.content.pm.PackageInfo
 import android.content.pm.ServiceInfo
 import android.os.Bundle
 import com.android.systemui.qs.external.PackageManagerAdapter
@@ -24,6 +25,7 @@
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import org.mockito.ArgumentMatchers.anyInt
 
 /**
  * Facade for [PackageManagerAdapter] to provide a fake-like behaviour. You can create this class
@@ -45,19 +47,33 @@
 
     init {
         whenever(packageManagerAdapter.getServiceInfo(eq(componentName), any())).thenAnswer {
-            ServiceInfo().apply {
-                metaData =
-                    Bundle().apply {
-                        putBoolean(
-                            android.service.quicksettings.TileService.META_DATA_TOGGLEABLE_TILE,
-                            isToggleable
-                        )
-                        putBoolean(
-                            android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE,
-                            isActive
-                        )
-                    }
-            }
+            createServiceInfo()
+        }
+        whenever(
+                packageManagerAdapter.getPackageInfoAsUser(
+                    eq(componentName.packageName),
+                    anyInt(),
+                    anyInt()
+                )
+            )
+            .thenAnswer { PackageInfo().apply { packageName = componentName.packageName } }
+        whenever(packageManagerAdapter.getServiceInfo(eq(componentName), anyInt(), anyInt()))
+            .thenAnswer { createServiceInfo() }
+    }
+
+    private fun createServiceInfo(): ServiceInfo {
+        return ServiceInfo().apply {
+            metaData =
+                Bundle().apply {
+                    putBoolean(
+                        android.service.quicksettings.TileService.META_DATA_TOGGLEABLE_TILE,
+                        isToggleable
+                    )
+                    putBoolean(
+                        android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE,
+                        isActive
+                    )
+                }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/night/NightDisplayTileKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/night/NightDisplayTileKosmos.kt
index bf22563..5c21ab6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/night/NightDisplayTileKosmos.kt
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.tiles.impl.night
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.qsNightDisplayTileConfig by
+    Kosmos.Fixture { QSAccessibilityModule.provideNightDisplayTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/onehanded/OneHandedModeTileKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/onehanded/OneHandedModeTileKosmos.kt
index bf22563..d9c0361 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/onehanded/OneHandedModeTileKosmos.kt
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.tiles.impl.onehanded
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.qsOneHandedModeTileConfig by
+    Kosmos.Fixture { QSAccessibilityModule.provideOneHandedTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt
new file mode 100644
index 0000000..dcfcce7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.qs.tiles.impl.qr
+
+import android.content.res.mainResources
+import com.android.systemui.classifier.fakeFalsingManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qrcodescanner.dagger.QRCodeScannerModule
+import com.android.systemui.qrcodescanner.qrCodeScannerController
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.analytics.qsTileAnalytics
+import com.android.systemui.qs.tiles.base.interactor.fakeDisabledByPolicyInteractor
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl
+import com.android.systemui.qs.tiles.impl.custom.qsTileLogger
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileDataInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.qr.ui.QRCodeScannerTileMapper
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.time.systemClock
+
+val Kosmos.qsQRCodeScannerTileConfig by
+    Kosmos.Fixture { QRCodeScannerModule.provideQRCodeScannerTileConfig(qsEventLogger) }
+
+val Kosmos.qrCodeScannerTileDataInteractor by
+    Kosmos.Fixture {
+        QRCodeScannerTileDataInteractor(
+            backgroundCoroutineContext,
+            applicationCoroutineScope,
+            qrCodeScannerController
+        )
+    }
+
+val Kosmos.qrCodeScannerTileUserActionInteractor by
+    Kosmos.Fixture { QRCodeScannerTileUserActionInteractor(qsTileIntentUserInputHandler) }
+
+val Kosmos.qrCodeScannerTileMapper by
+    Kosmos.Fixture { QRCodeScannerTileMapper(mainResources, mainResources.newTheme()) }
+
+val Kosmos.qsQRCodeScannerViewModel by
+    Kosmos.Fixture {
+        QSTileViewModelImpl(
+            qsQRCodeScannerTileConfig,
+            { qrCodeScannerTileUserActionInteractor },
+            { qrCodeScannerTileDataInteractor },
+            { qrCodeScannerTileMapper },
+            fakeDisabledByPolicyInteractor,
+            fakeUserRepository,
+            fakeFalsingManager,
+            qsTileAnalytics,
+            qsTileLogger,
+            systemClock,
+            testDispatcher,
+            testScope.backgroundScope,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt
new file mode 100644
index 0000000..641a757
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.scene.data.repository
+
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.model.Scenes
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+
+private val mutableTransitionState =
+    MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(Scenes.Lockscreen))
+
+fun Kosmos.setSceneTransition(
+    transition: ObservableTransitionState,
+    scope: TestScope = testScope,
+    repository: SceneContainerRepository = sceneContainerRepository
+) {
+    repository.setTransitionState(mutableTransitionState)
+    mutableTransitionState.value = transition
+    scope.runCurrent()
+}
+
+fun Transition(
+    from: SceneKey,
+    to: SceneKey,
+    currentScene: Flow<SceneKey> = flowOf(to),
+    progress: Flow<Float> = flowOf(0f),
+    isInitiatedByUserInput: Boolean = false,
+    isUserInputOngoing: Flow<Boolean> = flowOf(false),
+): ObservableTransitionState.Transition {
+    return ObservableTransitionState.Transition(
+        fromScene = from,
+        toScene = to,
+        currentScene = currentScene,
+        progress = progress,
+        isInitiatedByUserInput = isInitiatedByUserInput,
+        isUserInputOngoing = isUserInputOngoing
+    )
+}
+
+fun Idle(currentScene: SceneKey): ObservableTransitionState.Idle {
+    return ObservableTransitionState.Idle(currentScene)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt
index ebe591b..d82286f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.model.sysUiState
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.domain.startable.SceneContainerStartable
+import com.android.systemui.scene.session.shared.shadeSessionStorage
 import com.android.systemui.scene.shared.logger.sceneLogger
 import com.android.systemui.settings.displayTracker
 import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -65,5 +66,6 @@
         shadeInteractor = shadeInteractor,
         uiEventLogger = uiEventLogger,
         sceneBackInteractor = sceneBackInteractor,
+        shadeSessionStorage = shadeSessionStorage,
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/session/shared/SessionStorageKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/scene/session/shared/SessionStorageKosmos.kt
index bf22563..0bee937 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/session/shared/SessionStorageKosmos.kt
@@ -14,12 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.scene.session.shared
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+var Kosmos.shadeSessionStorage by Fixture { SessionStorage() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
index 59a01cb..957a60f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
@@ -42,6 +42,10 @@
         }
     }
 
+    override fun snapToScene(toScene: SceneKey) {
+        changeScene(toScene)
+    }
+
     /**
      * Pauses scene changes.
      *
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
index 59a5bb5..ea02d0c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
@@ -59,11 +59,33 @@
         delegate.setLockscreenShadeExpansion(lockscreenShadeExpansion)
     }
 
-    /** Sets whether the user is moving the shade with touch input. */
+    /** Sets whether the user is moving the shade with touch input on Lockscreen. */
     fun setLockscreenShadeTracking(lockscreenShadeTracking: Boolean) {
         delegate.assertFlagValid()
         delegate.setLockscreenShadeTracking(lockscreenShadeTracking)
     }
+
+    /** Sets whether the user is moving the shade with touch input. */
+    fun setTracking(tracking: Boolean) {
+        delegate.assertFlagValid()
+        delegate.setTracking(tracking)
+    }
+
+    /** Sets the shade to half collapsed with no touch input. */
+    fun programmaticCollapseShade() {
+        delegate.assertFlagValid()
+        delegate.programmaticCollapseShade()
+    }
+
+    fun setQsFullscreen(qsFullscreen: Boolean) {
+        delegate.assertFlagValid()
+        delegate.setQsFullscreen(qsFullscreen)
+    }
+
+    fun setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer: Boolean) {
+        delegate.assertFlagValid()
+        delegate.setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer)
+    }
 }
 
 /** Sets up shade state for tests for a specific value of the scene container flag. */
@@ -80,11 +102,21 @@
     /** Sets whether the user is moving the shade with touch input. */
     fun setLockscreenShadeTracking(lockscreenShadeTracking: Boolean)
 
+    /** Sets whether the user is moving the shade with touch input. */
+    fun setTracking(tracking: Boolean)
+
     /** Sets shade expansion to a value between 0-1. */
     fun setShadeExpansion(shadeExpansion: Float)
 
     /** Sets QS expansion to a value between 0-1. */
     fun setQsExpansion(qsExpansion: Float)
+
+    /** Sets the shade to half collapsed with no touch input. */
+    fun programmaticCollapseShade()
+
+    fun setQsFullscreen(qsFullscreen: Boolean)
+
+    fun setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer: Boolean)
 }
 
 /** Sets up shade state for tests when the scene container flag is disabled. */
@@ -104,6 +136,10 @@
         shadeRepository.setLegacyLockscreenShadeTracking(lockscreenShadeTracking)
     }
 
+    override fun setTracking(tracking: Boolean) {
+        shadeRepository.setLegacyShadeTracking(tracking)
+    }
+
     override fun assertFlagValid() {
         Assert.assertFalse(SceneContainerFlag.isEnabled)
     }
@@ -119,6 +155,19 @@
         shadeRepository.setQsExpansion(qsExpansion)
         testScope.runCurrent()
     }
+
+    override fun programmaticCollapseShade() {
+        shadeRepository.setLegacyShadeExpansion(.5f)
+        testScope.runCurrent()
+    }
+
+    override fun setQsFullscreen(qsFullscreen: Boolean) {
+        shadeRepository.legacyQsFullscreen.value = true
+    }
+
+    override fun setLegacyExpandedOrAwaitingInputTransfer(expanded: Boolean) {
+        shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(expanded)
+    }
 }
 
 /** Sets up shade state for tests when the scene container flag is enabled. */
@@ -127,14 +176,16 @@
     val isUserInputOngoing = MutableStateFlow(true)
 
     override fun setShadeAndQsExpansion(shadeExpansion: Float, qsExpansion: Float) {
-        if (shadeExpansion == 0f) {
-            setTransitionProgress(Scenes.Lockscreen, Scenes.QuickSettings, qsExpansion)
-        } else if (qsExpansion == 0f) {
-            setTransitionProgress(Scenes.Lockscreen, Scenes.Shade, shadeExpansion)
-        } else if (shadeExpansion == 1f) {
+        if (shadeExpansion == 1f) {
             setIdleScene(Scenes.Shade)
         } else if (qsExpansion == 1f) {
             setIdleScene(Scenes.QuickSettings)
+        } else if (shadeExpansion == 0f && qsExpansion == 0f) {
+            setIdleScene(Scenes.Lockscreen)
+        } else if (shadeExpansion == 0f) {
+            setTransitionProgress(Scenes.Lockscreen, Scenes.QuickSettings, qsExpansion)
+        } else if (qsExpansion == 0f) {
+            setTransitionProgress(Scenes.Lockscreen, Scenes.Shade, shadeExpansion)
         } else {
             setTransitionProgress(Scenes.Shade, Scenes.QuickSettings, qsExpansion)
         }
@@ -150,6 +201,20 @@
         setShadeAndQsExpansion(0f, qsExpansion)
     }
 
+    override fun programmaticCollapseShade() {
+        setTransitionProgress(Scenes.Shade, Scenes.Lockscreen, .5f, false)
+    }
+
+    override fun setQsFullscreen(qsFullscreen: Boolean) {
+        setQsExpansion(1f)
+    }
+
+    override fun setLegacyExpandedOrAwaitingInputTransfer(
+        legacyExpandedOrAwaitingInputTransfer: Boolean
+    ) {
+        setShadeExpansion(.1f)
+    }
+
     override fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float) {
         if (lockscreenShadeExpansion == 0f) {
             setIdleScene(Scenes.Lockscreen)
@@ -161,7 +226,11 @@
     }
 
     override fun setLockscreenShadeTracking(lockscreenShadeTracking: Boolean) {
-        isUserInputOngoing.value = lockscreenShadeTracking
+        setTracking(lockscreenShadeTracking)
+    }
+
+    override fun setTracking(tracking: Boolean) {
+        isUserInputOngoing.value = tracking
     }
 
     private fun setIdleScene(scene: SceneKey) {
@@ -172,7 +241,12 @@
         testScope.runCurrent()
     }
 
-    private fun setTransitionProgress(from: SceneKey, to: SceneKey, progress: Float) {
+    private fun setTransitionProgress(
+        from: SceneKey,
+        to: SceneKey,
+        progress: Float,
+        isInitiatedByUserInput: Boolean = true
+    ) {
         sceneInteractor.changeScene(from, "test")
         val transitionState =
             MutableStateFlow<ObservableTransitionState>(
@@ -181,7 +255,7 @@
                     toScene = to,
                     currentScene = flowOf(to),
                     progress = MutableStateFlow(progress),
-                    isInitiatedByUserInput = true,
+                    isInitiatedByUserInput = isInitiatedByUserInput,
                     isUserInputOngoing = isUserInputOngoing,
                 )
             )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
index 2bd584e..562ac0c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
@@ -27,6 +27,8 @@
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.logging.InstanceId;
 import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.SbnBuilder;
@@ -36,6 +38,7 @@
 import kotlin.Unit;
 
 import java.util.ArrayList;
+import java.util.function.Consumer;
 
 /**
  * Combined builder for constructing a NotificationEntry and its associated StatusBarNotification
@@ -73,6 +76,20 @@
         mCreationTime = source.getCreationTime();
     }
 
+    /** Allows the caller to sub-build the ranking */
+    @NonNull
+    public NotificationEntryBuilder updateRanking(@NonNull Consumer<RankingBuilder> rankingUpdater) {
+        rankingUpdater.accept(mRankingBuilder);
+        return this;
+    }
+
+    /** Allows the caller to sub-build the SBN */
+    @NonNull
+    public NotificationEntryBuilder updateSbn(@NonNull Consumer<SbnBuilder> sbnUpdater) {
+        sbnUpdater.accept(mSbnBuilder);
+        return this;
+    }
+
     /** Update an the parent on an existing entry */
     public static void setNewParent(NotificationEntry entry, GroupEntry parent) {
         entry.setParent(parent);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryKosmos.kt
new file mode 100644
index 0000000..3bb9580
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.content.applicationContext
+import com.android.systemui.animation.dialogTransitionAnimator
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.model.sysUiState
+
+val Kosmos.systemUIDialogFactory by
+    Kosmos.Fixture {
+        SystemUIDialogFactory(
+            applicationContext,
+            systemUIDialogManager,
+            sysUiState,
+            broadcastDispatcher,
+            dialogTransitionAnimator,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index cce038f..8229575 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -23,6 +23,7 @@
 import com.android.settingslib.mobile.MobileMappings
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
@@ -93,6 +94,8 @@
     private val _defaultMobileIconGroup = MutableStateFlow(DEFAULT_ICON)
     override val defaultMobileIconGroup = _defaultMobileIconGroup
 
+    override val deviceServiceState = MutableStateFlow<ServiceStateModel?>(null)
+
     override val isAnySimSecure = MutableStateFlow(false)
     override fun getIsAnySimSecure(): Boolean = isAnySimSecure.value
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index de6c87c2..3a4bf8e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -81,6 +81,8 @@
 
     override val isForceHidden = MutableStateFlow(false)
 
+    override val isDeviceInEmergencyCallsOnlyMode = MutableStateFlow(false)
+
     /** Always returns a new fake interactor */
     override fun getMobileConnectionInteractorForSubId(subId: Int): FakeMobileIconInteractor {
         return FakeMobileIconInteractor(tableLogBuffer).also {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 3e9ae4d..1f2ecb7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -37,7 +37,7 @@
 class FakeUserRepository @Inject constructor() : UserRepository {
     companion object {
         // User id to represent a non system (human) user id. We presume this is the main user.
-        private const val MAIN_USER_ID = 10
+        const val MAIN_USER_ID = 10
 
         private const val DEFAULT_SELECTED_USER = 0
         private val DEFAULT_SELECTED_USER_INFO =
@@ -84,6 +84,10 @@
 
     override var isRefreshUsersPaused: Boolean = false
 
+    override suspend fun getMainUserId(): Int? {
+        return MAIN_USER_ID
+    }
+
     var refreshUsersCallCount: Int = 0
         private set
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/MockExecutorHandler.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/MockExecutorHandler.kt
index 184d4b5..92f9248 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/MockExecutorHandler.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/MockExecutorHandler.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.util.concurrency
 
 import android.os.Handler
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.CopyOnWriteArrayList
 import java.util.concurrent.Executor
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyLong
@@ -29,9 +31,14 @@
  * Wrap an [Executor] in a mock [Handler] that execute when [Handler.post] is called, and throws an
  * exception otherwise. This is useful when a class requires a Handler only because Handlers are
  * used by ContentObserver, and no other methods are used.
+ *
+ * If the [executor] is a [DelayableExecutor], it also supports:
+ * * [Handler.postDelayed] with a Runnable parameter
+ * * [Handler.postAtTime] with a RunnableParameter
  */
 fun mockExecutorHandler(executor: Executor): Handler {
     val handlerMock = Mockito.mock(Handler::class.java, RuntimeExceptionAnswer())
+    val cancellations = ConcurrentHashMap<Runnable, MutableList<Cancellation>>()
     doAnswer { invocation: InvocationOnMock ->
             executor.execute(invocation.getArgument(0))
             true
@@ -42,7 +49,19 @@
         doAnswer { invocation: InvocationOnMock ->
                 val runnable = invocation.getArgument<Runnable>(0)
                 val uptimeMillis = invocation.getArgument<Long>(1)
-                executor.executeAtTime(runnable, uptimeMillis)
+                val token = Any()
+                val canceller =
+                    executor.executeAtTime(
+                        {
+                            cancellations.get(runnable)?.removeIf { it.token == token }
+                            cancellations.remove(runnable, emptyList())
+                            runnable.run()
+                        },
+                        uptimeMillis
+                    )
+                cancellations
+                    .getOrPut(runnable) { CopyOnWriteArrayList() }
+                    .add(Cancellation(token, canceller))
                 true
             }
             .`when`(handlerMock)
@@ -50,15 +69,36 @@
         doAnswer { invocation: InvocationOnMock ->
                 val runnable = invocation.getArgument<Runnable>(0)
                 val delayInMillis = invocation.getArgument<Long>(1)
-                executor.executeDelayed(runnable, delayInMillis)
+                val token = Any()
+                val canceller =
+                    executor.executeDelayed(
+                        {
+                            cancellations.get(runnable)?.removeIf { it.token == token }
+                            cancellations.remove(runnable, emptyList())
+                            runnable.run()
+                        },
+                        delayInMillis
+                    )
+                cancellations
+                    .getOrPut(runnable) { CopyOnWriteArrayList() }
+                    .add(Cancellation(token, canceller))
                 true
             }
             .`when`(handlerMock)
             .postDelayed(any(), anyLong())
+        doAnswer { invocation: InvocationOnMock ->
+                val runnable = invocation.getArgument<Runnable>(0)
+                cancellations.remove(runnable)?.forEach(Runnable::run)
+                Unit
+            }
+            .`when`(handlerMock)
+            .removeCallbacks(any())
     }
     return handlerMock
 }
 
+private class Cancellation(val token: Any, canceller: Runnable) : Runnable by canceller
+
 private class RuntimeExceptionAnswer : Answer<Any> {
     override fun answer(invocation: InvocationOnMock): Any {
         throw RuntimeException(invocation.method.name + " is not stubbed")
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
index d743558..1b58582 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
@@ -18,10 +18,7 @@
 
 import android.content.packageManager
 import android.content.pm.ApplicationInfo
-import android.os.Handler
-import android.testing.TestableLooper
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testCase
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.mediaOutputDialogManager
 import com.android.systemui.util.mockito.any
@@ -33,6 +30,7 @@
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.mediaControllerInteractor
 
 val Kosmos.localMediaRepository by Kosmos.Fixture { FakeLocalMediaRepository() }
 val Kosmos.localMediaRepositoryFactory by
@@ -54,7 +52,7 @@
             testScope.backgroundScope,
             testScope.testScheduler,
             mediaControllerRepository,
-            Handler(TestableLooper.get(testCase).looper),
+            mediaControllerInteractor,
         )
     }
 
@@ -62,7 +60,7 @@
     Kosmos.Fixture {
         MediaDeviceSessionInteractor(
             testScope.testScheduler,
-            Handler(TestableLooper.get(testCase).looper),
+            mediaControllerInteractor,
             mediaControllerRepository,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
index 617fc52..6b27079 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.volume.data.repository
 
 import android.media.AudioDeviceInfo
+import android.media.AudioManager
 import com.android.settingslib.volume.data.repository.AudioRepository
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.settingslib.volume.shared.model.AudioStreamModel
@@ -29,10 +30,10 @@
 
 class FakeAudioRepository : AudioRepository {
 
-    private val mutableMode = MutableStateFlow(0)
+    private val mutableMode = MutableStateFlow(AudioManager.MODE_NORMAL)
     override val mode: StateFlow<Int> = mutableMode.asStateFlow()
 
-    private val mutableRingerMode = MutableStateFlow(RingerMode(0))
+    private val mutableRingerMode = MutableStateFlow(RingerMode(AudioManager.RINGER_MODE_NORMAL))
     override val ringerMode: StateFlow<RingerMode> = mutableRingerMode.asStateFlow()
 
     private val mutableCommunicationDevice = MutableStateFlow<AudioDeviceInfo?>(null)
@@ -53,7 +54,7 @@
                     audioStream = audioStream,
                     volume = 0,
                     minVolume = 0,
-                    maxVolume = 0,
+                    maxVolume = 10,
                     isAffectedByRingerMode = false,
                     isMuted = false,
                 )
@@ -67,8 +68,14 @@
         getAudioStreamModelState(audioStream).update { it.copy(volume = volume) }
     }
 
-    override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) {
-        getAudioStreamModelState(audioStream).update { it.copy(isMuted = isMuted) }
+    override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean {
+        val modelState = getAudioStreamModelState(audioStream)
+        return if (modelState.value.isMuted == isMuted) {
+            false
+        } else {
+            modelState.update { it.copy(isMuted = isMuted) }
+            true
+        }
     }
 
     override suspend fun getLastAudibleVolume(audioStream: AudioStream): Int =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorKosmos.kt
new file mode 100644
index 0000000..d1d873e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.domain.interactor
+
+import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.domain.interactor.notificationsSoundPolicyInteractor
+import com.android.systemui.volume.data.repository.audioRepository
+
+val Kosmos.audioVolumeInteractor by
+    Kosmos.Fixture {
+        AudioVolumeInteractor(
+            audioRepository,
+            notificationsSoundPolicyInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
deleted file mode 100644
index 146f109..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
+++ /dev/null
@@ -1,78 +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.
- */
-
-package com.android.systemui.volume.panel
-
-import android.content.res.mainResources
-import com.android.systemui.broadcast.broadcastDispatcher
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.statusbar.policy.fakeConfigurationController
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.volume.panel.dagger.factory.KosmosVolumePanelComponentFactory
-import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
-import com.android.systemui.volume.panel.domain.TestComponentAvailabilityCriteria
-import com.android.systemui.volume.panel.domain.VolumePanelStartable
-import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
-import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractorImpl
-import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
-import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
-import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
-import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
-import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
-import javax.inject.Provider
-
-val Kosmos.mockVolumePanelUiComponent: VolumePanelUiComponent by Kosmos.Fixture { mock {} }
-val Kosmos.mockVolumePanelUiComponentProvider: Provider<VolumePanelUiComponent> by
-    Kosmos.Fixture { Provider { mockVolumePanelUiComponent } }
-var Kosmos.componentByKey: Map<VolumePanelComponentKey, Provider<VolumePanelUiComponent>> by
-    Kosmos.Fixture { emptyMap() }
-val Kosmos.componentsFactory: ComponentsFactory by
-    Kosmos.Fixture { ComponentsFactory(componentByKey) }
-
-var Kosmos.componentsLayoutManager: ComponentsLayoutManager by Kosmos.Fixture()
-var Kosmos.enabledComponents: Collection<VolumePanelComponentKey> by
-    Kosmos.Fixture { componentByKey.keys }
-var Kosmos.volumePanelStartables: Set<VolumePanelStartable> by
-    Kosmos.Fixture { emptySet<VolumePanelStartable>() }
-val Kosmos.unavailableCriteria: Provider<ComponentAvailabilityCriteria> by
-    Kosmos.Fixture { Provider { TestComponentAvailabilityCriteria(false) } }
-val Kosmos.availableCriteria: Provider<ComponentAvailabilityCriteria> by
-    Kosmos.Fixture { Provider { TestComponentAvailabilityCriteria(true) } }
-var Kosmos.defaultCriteria: Provider<ComponentAvailabilityCriteria> by
-    Kosmos.Fixture { availableCriteria }
-var Kosmos.criteriaByKey: Map<VolumePanelComponentKey, Provider<ComponentAvailabilityCriteria>> by
-    Kosmos.Fixture { emptyMap() }
-var Kosmos.componentsInteractor: ComponentsInteractor by
-    Kosmos.Fixture {
-        ComponentsInteractorImpl(
-            enabledComponents,
-            defaultCriteria,
-            testScope.backgroundScope,
-            criteriaByKey,
-        )
-    }
-
-var Kosmos.volumePanelViewModel: VolumePanelViewModel by
-    Kosmos.Fixture {
-        VolumePanelViewModel(
-            mainResources,
-            testScope.backgroundScope,
-            KosmosVolumePanelComponentFactory(this),
-            fakeConfigurationController,
-            broadcastDispatcher,
-        )
-    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/VolumePanelAncKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/VolumePanelAncKosmos.kt
index f9b7e69..2b5d1b9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/VolumePanelAncKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/VolumePanelAncKosmos.kt
@@ -20,10 +20,13 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.volume.domain.interactor.audioOutputInteractor
 import com.android.systemui.volume.panel.component.anc.data.repository.FakeAncSliceRepository
 import com.android.systemui.volume.panel.component.anc.domain.interactor.AncSliceInteractor
 
 var Kosmos.sliceViewManager: SliceViewManager by Kosmos.Fixture { mock {} }
 val Kosmos.ancSliceRepository by Kosmos.Fixture { FakeAncSliceRepository() }
 val Kosmos.ancSliceInteractor by
-    Kosmos.Fixture { AncSliceInteractor(ancSliceRepository, testScope.backgroundScope) }
+    Kosmos.Fixture {
+        AncSliceInteractor(audioOutputInteractor, ancSliceRepository, testScope.backgroundScope)
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt
index d4a72b4..ebe6850 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.volume.panel.component.anc.data.repository
 
+import android.bluetooth.BluetoothDevice
 import androidx.slice.Slice
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -24,7 +25,12 @@
 
     private val sliceByWidth = mutableMapOf<Int, MutableStateFlow<Slice?>>()
 
-    override fun ancSlice(width: Int, isCollapsed: Boolean, hideLabel: Boolean): Flow<Slice?> {
+    override fun ancSlice(
+        device: BluetoothDevice,
+        width: Int,
+        isCollapsed: Boolean,
+        hideLabel: Boolean
+    ): Flow<Slice?> {
         return sliceByWidth.getOrPut(width) { MutableStateFlow(null) }
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponentKosmos.kt
new file mode 100644
index 0000000..2ea27c7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponentKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.panel.component.bottombar.ui
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.panel.component.bottombar.ui.viewmodel.bottomBarViewModel
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.domain.availableCriteria
+
+var Kosmos.bottomBarComponent by Kosmos.Fixture { BottomBarComponent(bottomBarViewModel) }
+var Kosmos.bottomBarAvailabilityCriteria: ComponentAvailabilityCriteria by
+    Kosmos.Fixture { availableCriteria }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelKosmos.kt
new file mode 100644
index 0000000..128ede1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelKosmos.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.volume.panel.component.bottombar.ui.viewmodel
+
+import com.android.internal.logging.uiEventLogger
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.volume.panel.ui.viewmodel.volumePanelViewModel
+
+var Kosmos.bottomBarViewModel: BottomBarViewModel by
+    Kosmos.Fixture {
+        BottomBarViewModel(
+            activityStarter,
+            volumePanelViewModel,
+            uiEventLogger,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModuleKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModuleKosmos.kt
new file mode 100644
index 0000000..0c814c5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModuleKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.panel.component.mediaoutput
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.panel.component.mediaoutput.ui.composable.MediaOutputComponent
+import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.mediaOutputViewModel
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.domain.availableCriteria
+
+var Kosmos.mediaOutputComponent: MediaOutputComponent by
+    Kosmos.Fixture { MediaOutputComponent(mediaOutputViewModel) }
+var Kosmos.mediaOutputAvailabilityCriteria: ComponentAvailabilityCriteria by
+    Kosmos.Fixture { availableCriteria }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/FakeMediaControllerInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/FakeMediaControllerInteractor.kt
new file mode 100644
index 0000000..f03ec01
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/FakeMediaControllerInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.panel.component.mediaoutput.domain.interactor
+
+import android.media.session.MediaController
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+
+class FakeMediaControllerInteractor : MediaControllerInteractor {
+
+    private val stateChanges = MutableSharedFlow<MediaControllerChangeModel>(replay = 1)
+
+    override fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel> =
+        stateChanges
+
+    fun updateState(change: MediaControllerChangeModel) {
+        stateChanges.tryEmit(change)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt
index bf22563..652b3ea 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import android.os.Handler
+import android.os.looper
+import com.android.systemui.kosmos.Kosmos
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+var Kosmos.mediaControllerInteractor: MediaControllerInteractor by
+    Kosmos.Fixture { MediaControllerInteractorImpl(Handler(looper)) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt
new file mode 100644
index 0000000..4029609
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.panel.component.mediaoutput.domain.interactor
+
+import android.annotation.SuppressLint
+import android.bluetooth.BluetoothDevice
+import android.graphics.drawable.TestStubDrawable
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.media.BluetoothMediaDevice
+import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.media.PhoneMediaDevice
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+@SuppressLint("StaticFieldLeak") // These are mocks
+object TestMediaDevicesFactory {
+
+    fun builtInMediaDevice(): MediaDevice = mock {
+        whenever(name).thenReturn("built_in_media")
+        whenever(icon).thenReturn(TestStubDrawable())
+    }
+
+    fun wiredMediaDevice(): MediaDevice =
+        mock<PhoneMediaDevice> {
+            whenever(deviceType)
+                .thenReturn(MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE)
+            whenever(name).thenReturn("wired_media")
+            whenever(icon).thenReturn(TestStubDrawable())
+        }
+
+    fun bluetoothMediaDevice(): MediaDevice {
+        val bluetoothDevice = mock<BluetoothDevice>()
+        val cachedBluetoothDevice: CachedBluetoothDevice = mock {
+            whenever(isHearingAidDevice).thenReturn(true)
+            whenever(address).thenReturn("bt_media_device")
+            whenever(device).thenReturn(bluetoothDevice)
+        }
+        return mock<BluetoothMediaDevice> {
+            whenever(name).thenReturn("bt_media")
+            whenever(icon).thenReturn(TestStubDrawable())
+            whenever(cachedDevice).thenReturn(cachedBluetoothDevice)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelKosmos.kt
new file mode 100644
index 0000000..6d4576e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelKosmos.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.panel.component.mediaoutput.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.internal.logging.uiEventLogger
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.volume.domain.interactor.audioModeInteractor
+import com.android.systemui.volume.domain.interactor.audioOutputInteractor
+import com.android.systemui.volume.mediaDeviceSessionInteractor
+import com.android.systemui.volume.mediaOutputActionsInteractor
+import com.android.systemui.volume.mediaOutputInteractor
+
+var Kosmos.mediaOutputViewModel by
+    Kosmos.Fixture {
+        MediaOutputViewModel(
+            applicationContext,
+            testScope.backgroundScope,
+            mediaOutputActionsInteractor,
+            mediaDeviceSessionInteractor,
+            audioOutputInteractor,
+            audioModeInteractor,
+            mediaOutputInteractor,
+            uiEventLogger,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/VolumeModuleKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/VolumeModuleKosmos.kt
new file mode 100644
index 0000000..a17e745
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/VolumeModuleKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.panel.component.volume
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.panel.component.volume.ui.composable.VolumeSlidersComponent
+import com.android.systemui.volume.panel.component.volume.ui.viewmodel.audioVolumeComponentViewModel
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.domain.availableCriteria
+
+var Kosmos.volumeSlidersComponent: VolumeSlidersComponent by
+    Kosmos.Fixture { VolumeSlidersComponent(audioVolumeComponentViewModel) }
+var Kosmos.volumeSlidersAvailabilityCriteria: ComponentAvailabilityCriteria by
+    Kosmos.Fixture { availableCriteria }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt
new file mode 100644
index 0000000..a0a39d1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.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.volume.panel.component.volume.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.volume.data.repository.audioRepository
+import com.android.systemui.volume.mediaOutputInteractor
+
+val Kosmos.audioSlidersInteractor by
+    Kosmos.Fixture {
+        AudioSlidersInteractor(
+            testScope.backgroundScope,
+            mediaOutputInteractor,
+            audioRepository,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
new file mode 100644
index 0000000..b2b19de
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.panel.component.volume.slider.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.internal.logging.uiEventLogger
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.domain.interactor.audioVolumeInteractor
+import kotlinx.coroutines.CoroutineScope
+
+val Kosmos.audioStreamSliderViewModelFactory by
+    Kosmos.Fixture {
+        object : AudioStreamSliderViewModel.Factory {
+
+            override fun create(
+                audioStream: AudioStreamSliderViewModel.FactoryAudioStreamWrapper,
+                coroutineScope: CoroutineScope,
+            ): AudioStreamSliderViewModel {
+                return AudioStreamSliderViewModel(
+                    audioStream,
+                    coroutineScope,
+                    applicationContext,
+                    audioVolumeInteractor,
+                    uiEventLogger,
+                )
+            }
+        }
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt
new file mode 100644
index 0000000..f0cb2cd
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.panel.component.volume.slider.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.mediaDeviceSessionInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
+import kotlinx.coroutines.CoroutineScope
+
+val Kosmos.castVolumeSliderViewModelFactory by
+    Kosmos.Fixture {
+        object : CastVolumeSliderViewModel.Factory {
+            override fun create(
+                session: MediaDeviceSession,
+                coroutineScope: CoroutineScope
+            ): CastVolumeSliderViewModel {
+                return CastVolumeSliderViewModel(
+                    session,
+                    coroutineScope,
+                    applicationContext,
+                    mediaDeviceSessionInteractor,
+                )
+            }
+        }
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/VolumeSlidersViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/VolumeSlidersViewModelKosmos.kt
new file mode 100644
index 0000000..45a291e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/VolumeSlidersViewModelKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.panel.component.volume.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.volume.mediaDeviceSessionInteractor
+import com.android.systemui.volume.mediaOutputInteractor
+import com.android.systemui.volume.panel.component.volume.domain.interactor.audioSlidersInteractor
+import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.audioStreamSliderViewModelFactory
+import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.castVolumeSliderViewModelFactory
+
+val Kosmos.audioVolumeComponentViewModel by
+    Kosmos.Fixture {
+        AudioVolumeComponentViewModel(
+            testScope.backgroundScope,
+            mediaOutputInteractor,
+            mediaDeviceSessionInteractor,
+            audioStreamSliderViewModelFactory,
+            castVolumeSliderViewModelFactory,
+            audioSlidersInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt
index 1e895b5..587a7ea 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt
@@ -18,16 +18,16 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.volume.panel.componentsFactory
-import com.android.systemui.volume.panel.componentsInteractor
-import com.android.systemui.volume.panel.componentsLayoutManager
 import com.android.systemui.volume.panel.dagger.VolumePanelComponent
 import com.android.systemui.volume.panel.domain.VolumePanelStartable
 import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
+import com.android.systemui.volume.panel.domain.interactor.componentsInteractor
 import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
+import com.android.systemui.volume.panel.ui.composable.componentsFactory
 import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.layout.componentsLayoutManager
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
-import com.android.systemui.volume.panel.volumePanelStartables
+import com.android.systemui.volume.panel.ui.viewmodel.volumePanelStartables
 import kotlinx.coroutines.CoroutineScope
 
 class KosmosVolumePanelComponentFactory(private val kosmos: Kosmos) : VolumePanelComponentFactory {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/VolumePanelComponentFactoryKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/VolumePanelComponentFactoryKosmos.kt
index bf22563..b0b06f1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/VolumePanelComponentFactoryKosmos.kt
@@ -14,12 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.volume.panel.dagger.factory
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+var Kosmos.volumePanelComponentFactory by Kosmos.Fixture { KosmosVolumePanelComponentFactory(this) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/ComponentAvailabilityCriteriaKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/ComponentAvailabilityCriteriaKosmos.kt
new file mode 100644
index 0000000..e0547ee
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/ComponentAvailabilityCriteriaKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.panel.domain
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.unavailableCriteria: ComponentAvailabilityCriteria by
+    Kosmos.Fixture { TestComponentAvailabilityCriteria(false) }
+val Kosmos.availableCriteria: ComponentAvailabilityCriteria by
+    Kosmos.Fixture { AlwaysAvailableCriteria() }
+var Kosmos.defaultCriteria: ComponentAvailabilityCriteria by Kosmos.Fixture { availableCriteria }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt
new file mode 100644
index 0000000..8862942
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.panel.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.volume.panel.component.bottombar.ui.bottomBarAvailabilityCriteria
+import com.android.systemui.volume.panel.component.mediaoutput.mediaOutputAvailabilityCriteria
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.component.volume.volumeSlidersAvailabilityCriteria
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.domain.defaultCriteria
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.ui.composable.enabledComponents
+import javax.inject.Provider
+
+var Kosmos.criteriaByKey: Map<VolumePanelComponentKey, Provider<ComponentAvailabilityCriteria>> by
+    Kosmos.Fixture { emptyMap() }
+var Kosmos.prodCriteriaByKey:
+    Map<VolumePanelComponentKey, Provider<ComponentAvailabilityCriteria>> by
+    Kosmos.Fixture {
+        mapOf(
+            VolumePanelComponents.MEDIA_OUTPUT to Provider { mediaOutputAvailabilityCriteria },
+            VolumePanelComponents.VOLUME_SLIDERS to Provider { volumeSlidersAvailabilityCriteria },
+            VolumePanelComponents.BOTTOM_BAR to Provider { bottomBarAvailabilityCriteria },
+        )
+    }
+
+var Kosmos.componentsInteractor: ComponentsInteractor by
+    Kosmos.Fixture {
+        ComponentsInteractorImpl(
+            enabledComponents,
+            { defaultCriteria },
+            testScope.backgroundScope,
+            criteriaByKey,
+        )
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/shared/model/VolumePanelUiComponentKosmos.kt
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/shared/model/VolumePanelUiComponentKosmos.kt
index bf22563..afe8e62 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/shared/model/VolumePanelUiComponentKosmos.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.volume.panel.shared.model
 
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+import javax.inject.Provider
 
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
-    val isMediaVisible: Boolean
-        get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.mockVolumePanelUiComponent: VolumePanelUiComponent by Kosmos.Fixture { mock {} }
+val Kosmos.mockVolumePanelUiComponentProvider: Provider<VolumePanelUiComponent> by
+    Kosmos.Fixture { Provider { mock {} } }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryKosmos.kt
new file mode 100644
index 0000000..bacf22c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.panel.ui.composable
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.panel.component.bottombar.ui.bottomBarComponent
+import com.android.systemui.volume.panel.component.mediaoutput.mediaOutputComponent
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.component.volume.volumeSlidersComponent
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+import javax.inject.Provider
+
+var Kosmos.componentByKey: Map<VolumePanelComponentKey, Provider<VolumePanelUiComponent>> by
+    Kosmos.Fixture { emptyMap() }
+var Kosmos.prodComponentByKey: Map<VolumePanelComponentKey, Provider<VolumePanelUiComponent>> by
+    Kosmos.Fixture {
+        mapOf(
+            VolumePanelComponents.BOTTOM_BAR to Provider { bottomBarComponent },
+            VolumePanelComponents.MEDIA_OUTPUT to Provider { mediaOutputComponent },
+            VolumePanelComponents.VOLUME_SLIDERS to Provider { volumeSlidersComponent },
+        )
+    }
+var Kosmos.enabledComponents: Collection<VolumePanelComponentKey> by
+    Kosmos.Fixture { componentByKey.keys }
+
+val Kosmos.componentsFactory: ComponentsFactory by
+    Kosmos.Fixture { ComponentsFactory(componentByKey) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayoutManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayoutManagerKosmos.kt
new file mode 100644
index 0000000..e0480e7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayoutManagerKosmos.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.panel.ui.layout
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+
+var Kosmos.volumePanelBottomBarComponentKey: VolumePanelComponentKey by
+    Kosmos.Fixture { VolumePanelComponents.BOTTOM_BAR }
+var Kosmos.volumePanelHeaderComponentKeys: Collection<VolumePanelComponentKey> by
+    Kosmos.Fixture { listOf(VolumePanelComponents.MEDIA_OUTPUT) }
+var Kosmos.volumePanelFooterComponentKeys: Collection<VolumePanelComponentKey> by
+    Kosmos.Fixture {
+        listOf(
+            VolumePanelComponents.ANC,
+            VolumePanelComponents.SPATIAL_AUDIO,
+            VolumePanelComponents.CAPTIONING,
+        )
+    }
+
+var Kosmos.componentsLayoutManager: ComponentsLayoutManager by
+    Kosmos.Fixture {
+        DefaultComponentsLayoutManager(
+            bottomBar = volumePanelBottomBarComponentKey,
+            headerComponents = volumePanelHeaderComponentKeys,
+            footerComponents = volumePanelFooterComponentKeys,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt
new file mode 100644
index 0000000..a606588
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.panel.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.policy.configurationController
+import com.android.systemui.volume.panel.dagger.factory.volumePanelComponentFactory
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
+
+var Kosmos.volumePanelStartables: Set<VolumePanelStartable> by Kosmos.Fixture { emptySet() }
+
+var Kosmos.volumePanelViewModel: VolumePanelViewModel by
+    Kosmos.Fixture { volumePanelViewModelFactory.create(testScope.backgroundScope) }
+
+val Kosmos.volumePanelViewModelFactory: VolumePanelViewModel.Factory by
+    Kosmos.Fixture {
+        VolumePanelViewModel.Factory(
+            applicationContext,
+            volumePanelComponentFactory,
+            configurationController,
+            broadcastDispatcher,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorKosmos.kt
new file mode 100644
index 0000000..63b3f23
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.ui.navigation
+
+import com.android.internal.logging.uiEventLoggerFake
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.mainCoroutineContext
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.statusbar.phone.systemUIDialogFactory
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.volume.VolumePanelFactory
+import com.android.systemui.volume.panel.ui.viewmodel.volumePanelViewModelFactory
+
+val Kosmos.volumeNavigator by
+    Kosmos.Fixture {
+        VolumeNavigator(
+            testScope,
+            mainCoroutineContext,
+            mock<VolumePanelFactory> { /* Unsupported and unused */},
+            activityStarter,
+            volumePanelViewModelFactory,
+            systemUIDialogFactory,
+            uiEventLoggerFake,
+        )
+    }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt
index 2bc2db3..fe10244 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt
@@ -53,8 +53,8 @@
     @UnfoldMain
     fun provideMainRotationChangeProvider(
         rotationChangeProviderFactory: RotationChangeProvider.Factory,
-        @UnfoldMain mainHandler: Handler,
+        @UnfoldMain callbackHandler: Handler,
     ): RotationChangeProvider {
-        return rotationChangeProviderFactory.create(mainHandler)
+        return rotationChangeProviderFactory.create(callbackHandler)
     }
 }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
index 31b7ccc..f382070 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
@@ -87,6 +87,7 @@
             @BindsInstance @UnfoldMain executor: Executor,
             @BindsInstance @UnfoldMain handler: Handler,
             @BindsInstance @UnfoldSingleThreadBg singleThreadBgExecutor: Executor,
+            @BindsInstance @UnfoldBg bgHandler: Handler,
             @BindsInstance displayManager: DisplayManager,
             @BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String,
         ): RemoteUnfoldSharedComponent
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
index 1b7e71a..f83ea84 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
@@ -270,9 +270,9 @@
     @UnfoldMain
     fun provideRotationChangeProvider(
         rotationChangeProviderFactory: RotationChangeProvider.Factory,
-        @UnfoldMain mainHandler: Handler,
+        @UnfoldMain callbackHandler: Handler,
     ): RotationChangeProvider {
-        return rotationChangeProviderFactory.create(mainHandler)
+        return rotationChangeProviderFactory.create(callbackHandler)
     }
 
     @Provides
@@ -280,8 +280,9 @@
     @UnfoldBg
     fun provideBgRotationChangeProvider(
         rotationChangeProviderFactory: RotationChangeProvider.Factory,
-        @UnfoldBg bgHandler: Handler,
+        @UnfoldBg callbackHandler: Handler,
     ): RotationChangeProvider {
-        return rotationChangeProviderFactory.create(bgHandler)
+        // For UnfoldBg RotationChangeProvider we use bgHandler as callbackHandler
+        return rotationChangeProviderFactory.create(callbackHandler)
     }
 }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
index 1cbaf31..8a4f985 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -77,6 +77,7 @@
         mainExecutor: Executor,
         mainHandler: Handler,
         singleThreadBgExecutor: Executor,
+        bgHandler: Handler,
         tracingTagPrefix: String,
         displayManager: DisplayManager,
         ): RemoteUnfoldSharedComponent =
@@ -87,6 +88,7 @@
                         mainExecutor,
                         mainHandler,
                         singleThreadBgExecutor,
+                        bgHandler,
                         displayManager,
                         tracingTagPrefix,
                 )
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 77f637b..a100974 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -20,6 +20,7 @@
 import android.util.Log
 import androidx.annotation.FloatRange
 import androidx.annotation.VisibleForTesting
+import androidx.annotation.WorkerThread
 import androidx.core.util.Consumer
 import com.android.systemui.unfold.compat.INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
@@ -215,6 +216,7 @@
     }
 
     private inner class FoldRotationListener : RotationChangeProvider.RotationListener {
+        @WorkerThread
         override fun onRotationChanged(newRotation: Int) {
             assertInProgressThread()
             if (isTransitionInProgress) cancelAnimation()
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
index bb91f9b..4f3aee9 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
@@ -21,6 +21,8 @@
 import android.os.Handler
 import android.os.RemoteException
 import android.os.Trace
+import androidx.annotation.AnyThread
+import com.android.systemui.unfold.dagger.UnfoldBg
 import com.android.systemui.unfold.util.CallbackController
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
@@ -35,7 +37,8 @@
 constructor(
     private val displayManager: DisplayManager,
     private val context: Context,
-    @Assisted private val handler: Handler,
+    @UnfoldBg private val bgHandler: Handler,
+    @Assisted private val callbackHandler: Handler,
 ) : CallbackController<RotationChangeProvider.RotationListener> {
 
     private val listeners = mutableListOf<RotationListener>()
@@ -44,7 +47,7 @@
     private var lastRotation: Int? = null
 
     override fun addCallback(listener: RotationListener) {
-        handler.post {
+        bgHandler.post {
             if (listeners.isEmpty()) {
                 subscribeToRotation()
             }
@@ -53,7 +56,7 @@
     }
 
     override fun removeCallback(listener: RotationListener) {
-        handler.post {
+        bgHandler.post {
             listeners -= listener
             if (listeners.isEmpty()) {
                 unsubscribeToRotation()
@@ -64,7 +67,7 @@
 
     private fun subscribeToRotation() {
         try {
-            displayManager.registerDisplayListener(displayListener, handler)
+            displayManager.registerDisplayListener(displayListener, callbackHandler)
         } catch (e: RemoteException) {
             throw e.rethrowFromSystemServer()
         }
@@ -80,8 +83,11 @@
 
     /** Gets notified of rotation changes. */
     fun interface RotationListener {
-        /** Called once rotation changes. */
-        fun onRotationChanged(newRotation: Int)
+        /**
+         * Called once rotation changes. This callback is called on the handler provided to
+         * [RotationChangeProvider.Factory.create].
+         */
+        @AnyThread fun onRotationChanged(newRotation: Int)
     }
 
     private inner class RotationDisplayListener : DisplayManager.DisplayListener {
@@ -110,7 +116,7 @@
 
     @AssistedFactory
     interface Factory {
-        /** Creates a new [RotationChangeProvider] that provides updated using [handler]. */
-        fun create(handler: Handler): RotationChangeProvider
+        /** Creates a new [RotationChangeProvider] that provides updated using [callbackHandler]. */
+        fun create(callbackHandler: Handler): RotationChangeProvider
     }
 }
diff --git a/proto/src/am_capabilities.proto b/proto/src/am_capabilities.proto
index fc9f7a45..c2b3ac2 100644
--- a/proto/src/am_capabilities.proto
+++ b/proto/src/am_capabilities.proto
@@ -15,8 +15,16 @@
   string name  = 1;
 }
 
+message VMInfo {
+  // The value of the "java.vm.name" system property
+  string name = 1;
+  // The value of the "java.vm.version" system property
+  string version = 2;
+}
+
 message Capabilities {
   repeated Capability values = 1;
   repeated VMCapability vm_capabilities = 2;
   repeated FrameworkCapability framework_capabilities = 3;
+  VMInfo vm_info = 4;
 }
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index e06f400..95cbb6b 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -16,6 +16,38 @@
     visibility: ["//visibility:public"],
 }
 
+filegroup {
+    name: "ravenwood-services-policies",
+    srcs: [
+        "texts/ravenwood-services-policies.txt",
+    ],
+    visibility: ["//visibility:public"],
+}
+
+filegroup {
+    name: "ravenwood-framework-policies",
+    srcs: [
+        "texts/ravenwood-framework-policies.txt",
+    ],
+    visibility: ["//visibility:public"],
+}
+
+filegroup {
+    name: "ravenwood-standard-options",
+    srcs: [
+        "texts/ravenwood-standard-options.txt",
+    ],
+    visibility: ["//visibility:public"],
+}
+
+filegroup {
+    name: "ravenwood-annotation-allowed-classes",
+    srcs: [
+        "texts/ravenwood-annotation-allowed-classes.txt",
+    ],
+    visibility: ["//visibility:public"],
+}
+
 java_library {
     name: "ravenwood-annotations-lib",
     srcs: [":ravenwood-annotations"],
@@ -189,7 +221,9 @@
     data: [
         ":framework-minus-apex.ravenwood.stats",
         ":framework-minus-apex.ravenwood.apis",
+        ":framework-minus-apex.ravenwood.keep_all",
         ":services.core.ravenwood.stats",
         ":services.core.ravenwood.apis",
+        ":services.core.ravenwood.keep_all",
     ],
 }
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodBaseContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodBaseContext.java
new file mode 100644
index 0000000..4992c4b
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodBaseContext.java
@@ -0,0 +1,753 @@
+/*
+ * 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.platform.test.ravenwood;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.IntentSender.SendIntentException;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.database.DatabaseErrorHandler;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDatabase.CursorFactory;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.view.Display;
+import android.view.DisplayAdjustments;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A subclass of Context with all the abstract methods replaced with concrete methods.
+ *
+ * <p>In order to make sure it implements all the abstract methods, we intentionally keep it
+ * non-abstract.
+ */
+public class RavenwoodBaseContext extends Context {
+    RavenwoodBaseContext() {
+        // Only usable by ravenwood.
+    }
+
+    private static RuntimeException notSupported() {
+        return new RuntimeException("This Context API is not yet supported under"
+                + " the Ravenwood deviceless testing environment. Contact g/ravenwood");
+    }
+
+    @Override
+    public AssetManager getAssets() {
+        throw notSupported();
+    }
+
+    @Override
+    public Resources getResources() {
+        throw notSupported();
+    }
+
+    @Override
+    public PackageManager getPackageManager() {
+        throw notSupported();
+    }
+
+    @Override
+    public ContentResolver getContentResolver() {
+        throw notSupported();
+    }
+
+    @Override
+    public Looper getMainLooper() {
+        throw notSupported();
+    }
+
+    @Override
+    public Context getApplicationContext() {
+        throw notSupported();
+    }
+
+    @Override
+    public void setTheme(int resid) {
+        throw notSupported();
+    }
+
+    @Override
+    public Theme getTheme() {
+        throw notSupported();
+    }
+
+    @Override
+    public ClassLoader getClassLoader() {
+        throw notSupported();
+    }
+
+    @Override
+    public String getPackageName() {
+        throw notSupported();
+    }
+
+    @Override
+    public String getBasePackageName() {
+        throw notSupported();
+    }
+
+    @Override
+    public ApplicationInfo getApplicationInfo() {
+        throw notSupported();
+    }
+
+    @Override
+    public String getPackageResourcePath() {
+        throw notSupported();
+    }
+
+    @Override
+    public String getPackageCodePath() {
+        throw notSupported();
+    }
+
+    @Override
+    public SharedPreferences getSharedPreferences(String name, int mode) {
+        throw notSupported();
+    }
+
+    @Override
+    public SharedPreferences getSharedPreferences(File file, int mode) {
+        throw notSupported();
+    }
+
+    @Override
+    public boolean moveSharedPreferencesFrom(Context sourceContext, String name) {
+        throw notSupported();
+    }
+
+    @Override
+    public boolean deleteSharedPreferences(String name) {
+        throw notSupported();
+    }
+
+    @Override
+    public void reloadSharedPreferences() {
+        throw notSupported();
+    }
+
+    @Override
+    public FileInputStream openFileInput(String name) throws FileNotFoundException {
+        throw notSupported();
+    }
+
+    @Override
+    public FileOutputStream openFileOutput(String name, int mode) throws FileNotFoundException {
+        throw notSupported();
+    }
+
+    @Override
+    public boolean deleteFile(String name) {
+        throw notSupported();
+    }
+
+    @Override
+    public File getFileStreamPath(String name) {
+        throw notSupported();
+    }
+
+    @Override
+    public File getSharedPreferencesPath(String name) {
+        throw notSupported();
+    }
+
+    @Override
+    public File getDataDir() {
+        throw notSupported();
+    }
+
+    @Override
+    public File getFilesDir() {
+        throw notSupported();
+    }
+
+    @Override
+    public File getNoBackupFilesDir() {
+        throw notSupported();
+    }
+
+    @Override
+    public File getExternalFilesDir(String type) {
+        throw notSupported();
+    }
+
+    @Override
+    public File[] getExternalFilesDirs(String type) {
+        throw notSupported();
+    }
+
+    @Override
+    public File getObbDir() {
+        throw notSupported();
+    }
+
+    @Override
+    public File[] getObbDirs() {
+        throw notSupported();
+    }
+
+    @Override
+    public File getCacheDir() {
+        throw notSupported();
+    }
+
+    @Override
+    public File getCodeCacheDir() {
+        throw notSupported();
+    }
+
+    @Override
+    public File getExternalCacheDir() {
+        throw notSupported();
+    }
+
+    @Override
+    public File getPreloadsFileCache() {
+        throw notSupported();
+    }
+
+    @Override
+    public File[] getExternalCacheDirs() {
+        throw notSupported();
+    }
+
+    @Override
+    public File[] getExternalMediaDirs() {
+        throw notSupported();
+    }
+
+    @Override
+    public String[] fileList() {
+        throw notSupported();
+    }
+
+    @Override
+    public File getDir(String name, int mode) {
+        throw notSupported();
+    }
+
+    @Override
+    public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory) {
+        throw notSupported();
+    }
+
+    @Override
+    public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory,
+            DatabaseErrorHandler errorHandler) {
+        throw notSupported();
+    }
+
+    @Override
+    public boolean moveDatabaseFrom(Context sourceContext, String name) {
+        throw notSupported();
+    }
+
+    @Override
+    public boolean deleteDatabase(String name) {
+        throw notSupported();
+    }
+
+    @Override
+    public File getDatabasePath(String name) {
+        throw notSupported();
+    }
+
+    @Override
+    public String[] databaseList() {
+        throw notSupported();
+    }
+
+    @Override
+    public Drawable getWallpaper() {
+        throw notSupported();
+    }
+
+    @Override
+    public Drawable peekWallpaper() {
+        throw notSupported();
+    }
+
+    @Override
+    public int getWallpaperDesiredMinimumWidth() {
+        throw notSupported();
+    }
+
+    @Override
+    public int getWallpaperDesiredMinimumHeight() {
+        throw notSupported();
+    }
+
+    @Override
+    public void setWallpaper(Bitmap bitmap) throws IOException {
+        throw notSupported();
+    }
+
+    @Override
+    public void setWallpaper(InputStream data) throws IOException {
+        throw notSupported();
+    }
+
+    @Override
+    public void clearWallpaper() throws IOException {
+        throw notSupported();
+    }
+
+    @Override
+    public void startActivity(Intent intent) {
+        throw notSupported();
+    }
+
+    @Override
+    public void startActivity(Intent intent, Bundle options) {
+        throw notSupported();
+    }
+
+    @Override
+    public void startActivities(Intent[] intents) {
+        throw notSupported();
+    }
+
+    @Override
+    public void startActivities(Intent[] intents, Bundle options) {
+        throw notSupported();
+    }
+
+    @Override
+    public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask,
+            int flagsValues, int extraFlags) throws SendIntentException {
+        throw notSupported();
+    }
+
+    @Override
+    public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask,
+            int flagsValues, int extraFlags, Bundle options) throws SendIntentException {
+        throw notSupported();
+    }
+
+    @Override
+    public void sendBroadcast(Intent intent) {
+        throw notSupported();
+    }
+
+    @Override
+    public void sendBroadcast(Intent intent, String receiverPermission) {
+        throw notSupported();
+    }
+
+    @Override
+    public void sendBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user,
+            String[] receiverPermissions) {
+        throw notSupported();
+    }
+
+    @Override
+    public void sendBroadcast(Intent intent, String receiverPermission, int appOp) {
+        throw notSupported();
+    }
+
+    @Override
+    public void sendOrderedBroadcast(Intent intent, String receiverPermission) {
+        throw notSupported();
+    }
+
+    @Override
+    public void sendOrderedBroadcast(Intent intent, String receiverPermission,
+            BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
+            String initialData, Bundle initialExtras) {
+        throw notSupported();
+    }
+
+    @Override
+    public void sendOrderedBroadcast(Intent intent, String receiverPermission,
+            int appOp, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
+            String initialData, Bundle initialExtras) {
+        throw notSupported();
+    }
+
+    @Override
+    public void sendBroadcastAsUser(Intent intent, UserHandle user) {
+        throw notSupported();
+    }
+
+    @Override
+    public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission) {
+        throw notSupported();
+    }
+
+    @Override
+    public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission,
+            Bundle options) {
+        throw notSupported();
+    }
+
+    @Override
+    public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission,
+            int appOp) {
+        throw notSupported();
+    }
+
+    @Override
+    public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+            String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
+            int initialCode, String initialData, Bundle initialExtras) {
+        throw notSupported();
+    }
+
+    @Override
+    public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+            String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
+            Handler scheduler, int initialCode, String initialData, Bundle initialExtras) {
+        throw notSupported();
+    }
+
+    @Override
+    public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+            String receiverPermission, int appOp, Bundle options,
+            BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
+            String initialData, Bundle initialExtras) {
+        throw notSupported();
+    }
+
+    @Override
+    public void sendStickyBroadcast(Intent intent) {
+        throw notSupported();
+    }
+
+    @Override
+    public void sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver resultReceiver,
+            Handler scheduler, int initialCode, String initialData, Bundle initialExtras) {
+        throw notSupported();
+
+    }
+
+    @Override
+    public void removeStickyBroadcast(Intent intent) {
+        throw notSupported();
+
+    }
+
+    @Override
+    public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) {
+        throw notSupported();
+    }
+
+    @Override
+    public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) {
+        throw notSupported();
+
+    }
+
+    @Override
+    public void sendStickyOrderedBroadcastAsUser(Intent intent, UserHandle user,
+            BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
+            String initialData, Bundle initialExtras) {
+        throw notSupported();
+    }
+
+    @Override
+    public void removeStickyBroadcastAsUser(Intent intent, UserHandle user) {
+        throw notSupported();
+    }
+
+    @Override
+    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+        throw notSupported();
+    }
+
+    @Override
+    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {
+        throw notSupported();
+    }
+
+    @Override
+    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+            String broadcastPermission, Handler scheduler) {
+        throw notSupported();
+    }
+
+    @Override
+    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+            String broadcastPermission, Handler scheduler, int flags) {
+        throw notSupported();
+    }
+
+    @Override
+    public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
+            IntentFilter filter, String broadcastPermission, Handler scheduler) {
+        throw notSupported();
+    }
+
+    @Override
+    public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
+            IntentFilter filter, String broadcastPermission, Handler scheduler, int flags) {
+        throw notSupported();
+    }
+
+    @Override
+    public void unregisterReceiver(BroadcastReceiver receiver) {
+        throw notSupported();
+    }
+
+    @Override
+    public ComponentName startService(Intent service) {
+        throw notSupported();
+    }
+
+    @Override
+    public ComponentName startForegroundService(Intent service) {
+        throw notSupported();
+    }
+
+    @Override
+    public ComponentName startForegroundServiceAsUser(Intent service, UserHandle user) {
+        throw notSupported();
+    }
+
+    @Override
+    public boolean stopService(Intent service) {
+        throw notSupported();
+    }
+
+    @Override
+    public ComponentName startServiceAsUser(Intent service, UserHandle user) {
+        throw notSupported();
+    }
+
+    @Override
+    public boolean stopServiceAsUser(Intent service, UserHandle user) {
+        throw notSupported();
+    }
+
+    @Override
+    public boolean bindService(Intent service, ServiceConnection conn, int flags) {
+        throw notSupported();
+    }
+
+    @Override
+    public void unbindService(ServiceConnection conn) {
+        throw notSupported();
+    }
+
+    @Override
+    public boolean startInstrumentation(ComponentName className, String profileFile,
+            Bundle arguments) {
+        throw notSupported();
+    }
+
+    @Override
+    public Object getSystemService(String name) {
+        throw notSupported();
+    }
+
+    @Override
+    public String getSystemServiceName(Class<?> serviceClass) {
+        throw notSupported();
+    }
+
+    @Override
+    public int checkPermission(String permission, int pid, int uid) {
+        throw notSupported();
+    }
+
+    @Override
+    public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
+        throw notSupported();
+    }
+
+    @Override
+    public int checkCallingPermission(String permission) {
+        throw notSupported();
+    }
+
+    @Override
+    public int checkCallingOrSelfPermission(String permission) {
+        throw notSupported();
+    }
+
+    @Override
+    public int checkSelfPermission(String permission) {
+        throw notSupported();
+    }
+
+    @Override
+    public void enforcePermission(String permission, int pid, int uid, String message) {
+        throw notSupported();
+    }
+
+    @Override
+    public void enforceCallingPermission(String permission, String message) {
+        throw notSupported();
+    }
+
+    @Override
+    public void enforceCallingOrSelfPermission(String permission, String message) {
+        throw notSupported();
+    }
+
+    @Override
+    public void grantUriPermission(String toPackage, Uri uri, int modeFlags) {
+        throw notSupported();
+    }
+
+    @Override
+    public void revokeUriPermission(Uri uri, int modeFlags) {
+        throw notSupported();
+    }
+
+    @Override
+    public void revokeUriPermission(String toPackage, Uri uri, int modeFlags) {
+        throw notSupported();
+    }
+
+    @Override
+    public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
+        throw notSupported();
+    }
+
+    @Override
+    public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) {
+        throw notSupported();
+    }
+
+    @Override
+    public int checkCallingUriPermission(Uri uri, int modeFlags) {
+        throw notSupported();
+    }
+
+    @Override
+    public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) {
+        throw notSupported();
+    }
+
+    @Override
+    public int checkUriPermission(Uri uri, String readPermission, String writePermission,
+            int pid, int uid, int modeFlags) {
+        throw notSupported();
+    }
+
+    @Override
+    public void enforceUriPermission(Uri uri, int pid, int uid, int modeFlags, String message) {
+        throw notSupported();
+    }
+
+    @Override
+    public void enforceCallingUriPermission(Uri uri, int modeFlags, String message) {
+        throw notSupported();
+    }
+
+    @Override
+    public void enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message) {
+        throw notSupported();
+    }
+
+    @Override
+    public void enforceUriPermission(Uri uri, String readPermission, String writePermission,
+            int pid, int uid, int modeFlags, String message) {
+        throw notSupported();
+    }
+
+    @Override
+    public Context createPackageContext(String packageName, int flags)
+            throws NameNotFoundException {
+        throw notSupported();
+    }
+
+    @Override
+    public Context createApplicationContext(ApplicationInfo application, int flags)
+            throws NameNotFoundException {
+        throw notSupported();
+    }
+
+    @Override
+    public Context createContextForSplit(String splitName) throws NameNotFoundException {
+        throw notSupported();
+    }
+
+    @Override
+    public Context createConfigurationContext(Configuration overrideConfiguration) {
+        throw notSupported();
+    }
+
+    @Override
+    public Context createDisplayContext(Display display) {
+        throw notSupported();
+    }
+
+    @Override
+    public Context createDeviceProtectedStorageContext() {
+        throw notSupported();
+    }
+
+    @Override
+    public Context createCredentialProtectedStorageContext() {
+        throw notSupported();
+    }
+
+    @Override
+    public DisplayAdjustments getDisplayAdjustments(int displayId) {
+        throw notSupported();
+    }
+
+    @Override
+    public int getDisplayId() {
+        throw notSupported();
+    }
+
+    @Override
+    public void updateDisplay(int displayId) {
+        throw notSupported();
+    }
+
+    @Override
+    public boolean isDeviceProtectedStorage() {
+        throw notSupported();
+    }
+
+    @Override
+    public boolean isCredentialProtectedStorage() {
+        throw notSupported();
+    }
+
+    @Override
+    public boolean canLoadUnsafeResources() {
+        throw notSupported();
+    }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
index 109ef76..1dd5e1d 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
@@ -28,7 +28,6 @@
 import android.os.UserHandle;
 import android.ravenwood.example.BlueManager;
 import android.ravenwood.example.RedManager;
-import android.test.mock.MockContext;
 import android.util.ArrayMap;
 import android.util.Singleton;
 
@@ -36,7 +35,7 @@
 import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
-public class RavenwoodContext extends MockContext {
+public class RavenwoodContext extends RavenwoodBaseContext {
     private final String mPackageName;
     private final HandlerThread mMainThread;
 
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
index 96b7057..69ff262 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
@@ -162,20 +162,23 @@
             android.graphics.Interpolator.class,
             android.graphics.Matrix.class,
             android.graphics.Path.class,
+            android.graphics.Color.class,
+            android.graphics.ColorSpace.class,
     };
 
     /**
-     * @return if a given class has any native method or not.
+     * @return if a given class and its nested classes, if any, have any native method or not.
      */
     private static boolean hasNativeMethod(Class<?> clazz) {
-        for (var method : clazz.getDeclaredMethods()) {
-            if (Modifier.isNative(method.getModifiers())) {
-                return true;
+        for (var nestedClass : clazz.getNestMembers()) {
+            for (var method : nestedClass.getDeclaredMethods()) {
+                if (Modifier.isNative(method.getModifiers())) {
+                    return true;
+                }
             }
         }
         return false;
     }
-
     /**
      * Create a list of classes as comma-separated that require JNI methods to be set up from
      * a given class list, ignoring classes with no native methods.
diff --git a/ravenwood/scripts/convert-androidtest.py b/ravenwood/scripts/convert-androidtest.py
new file mode 100755
index 0000000..61ec54b
--- /dev/null
+++ b/ravenwood/scripts/convert-androidtest.py
@@ -0,0 +1,184 @@
+#!/usr/bin/python3
+# 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.
+
+# This script converts a legacy test class (using AndroidTestCase, TestCase or
+# InstrumentationTestCase to a modern style test class, in a best-effort manner.
+#
+# Usage:
+#  convert-androidtest.py TARGET-FILE [TARGET-FILE ...]
+#
+# Caveats:
+#   - It adds all the extra imports, even if they're not needed.
+#   - It won't sort imports.
+#   - It also always adds getContext() and getTestContext().
+#
+
+import sys
+import fileinput
+import re
+import subprocess
+
+# Print message on console
+def log(msg):
+    print(msg, file=sys.stderr)
+
+
+# Matches `extends AndroidTestCase` (or another similar base class)
+re_extends = re.compile(
+    r''' \b extends \s+ (AndroidTestCase|TestCase|InstrumentationTestCase) \s* ''',
+    re.S + re.X)
+
+
+# Look into given files and return the files that have `re_extends`.
+def find_target_files(files):
+    ret = []
+
+    for file in files:
+        try:
+            with open(file, 'r') as f:
+                data = f.read()
+
+                if re_extends.search(data):
+                    ret.append(file)
+
+        except FileNotFoundError as e:
+            log(f'Failed to open file {file}: {e}')
+
+    return ret
+
+
+def main(args):
+    files = args
+
+    # Find the files that should be processed.
+    files = find_target_files(files)
+
+    if len(files) == 0:
+        log("No target files found.")
+        return 0
+
+    # Process the files.
+    with fileinput.input(files=(files), inplace = True, backup = '.bak') as f:
+        import_seen = False
+        carry_over = ''
+        class_body_started = False
+        class_seen = False
+
+        def on_file_start():
+            nonlocal import_seen, carry_over, class_body_started, class_seen
+            import_seen = False
+            carry_over = ''
+            class_body_started = False
+            class_seen = False
+
+        for line in f:
+            if (fileinput.filelineno() == 1):
+                log(f"Processing: {fileinput.filename()}")
+                on_file_start()
+
+            line = line.rstrip('\n')
+
+            # Carry over a certain line to the next line.
+            if re.search(r'''@Override\b''', line):
+                carry_over = carry_over + line + '\n'
+                continue
+
+            if carry_over:
+                line = carry_over + line
+                carry_over = ''
+
+
+            # Remove the base class from the class definition.
+            line = re_extends.sub('', line)
+
+            # Add a @RunWith.
+            if not class_seen and re.search(r'''\b class \b''', line, re.X):
+                class_seen = True
+                print("@RunWith(AndroidJUnit4.class)")
+
+
+            # Inject extra imports.
+            if not import_seen and re.search(r'''^import\b''', line):
+                import_seen = True
+                print("""\
+import android.content.Context;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertSame;
+import static junit.framework.TestCase.assertNotSame;
+import static junit.framework.TestCase.assertTrue;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertNull;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.fail;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+""")
+
+            # Add @Test to the test methods.
+            if re.search(r'''^ \s* public \s* void \s* test''', line, re.X):
+                print("    @Test")
+
+            # Convert setUp/tearDown to @Before/@After.
+            if re.search(r''' ^\s+ ( \@Override \s+ ) ? (public|protected) \s+ void \s+ (setUp|tearDown) ''',
+                        line, re.X):
+                if re.search('setUp', line):
+                    print('    @Before')
+                else:
+                    print('    @After')
+
+                line = re.sub(r''' \s* \@Override \s* \n ''', '', line, 0, re.X)
+                line = re.sub(r'''protected''', 'public', line, 0, re.X)
+
+            # Remove the super setUp / tearDown call.
+            if re.search(r''' \b super \. (setUp|tearDown) \b ''', line, re.X):
+                continue
+
+            # Convert mContext to getContext().
+            line = re.sub(r'''\b mContext \b ''', 'getContext()', line, 0, re.X)
+
+            # Print the processed line.
+            print(line)
+
+            # Add getContext() / getTestContext() at the beginning of the class.
+            if not class_body_started and re.search(r'''\{''', line):
+                class_body_started = True
+                print("""\
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    private Context getTestContext() {
+        return InstrumentationRegistry.getInstrumentation().getContext();
+    }
+""")
+
+
+    # Run diff
+    for file in files:
+        subprocess.call(["diff", "-u", "--color=auto", f"{file}.bak", file])
+
+    log(f'{len(files)} file(s) converted.')
+
+    return 0
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv[1:]))
diff --git a/ravenwood/scripts/ravenwood-stats-collector.sh b/ravenwood/scripts/ravenwood-stats-collector.sh
index beacde2..43b61a4 100755
--- a/ravenwood/scripts/ravenwood-stats-collector.sh
+++ b/ravenwood/scripts/ravenwood-stats-collector.sh
@@ -18,12 +18,20 @@
 set -e
 
 # Output files
-stats=/tmp/ravenwood-stats-all.csv
-apis=/tmp/ravenwood-apis-all.csv
+out_dir=/tmp/ravenwood
+stats=$out_dir/ravenwood-stats-all.csv
+apis=$out_dir/ravenwood-apis-all.csv
+keep_all_dir=$out_dir/ravenwood-keep-all/
+
+rm -fr $out_dir
+mkdir -p $out_dir
+mkdir -p $keep_all_dir
 
 # Where the input files are.
 path=$ANDROID_BUILD_TOP/out/host/linux-x86/testcases/ravenwood-stats-checker/x86_64/
 
+timestamp="$(date --iso-8601=seconds)"
+
 m() {
     ${ANDROID_BUILD_TOP}/build/soong/soong_ui.bash --make-mode "$@"
 }
@@ -39,15 +47,15 @@
     local jar=$1
     local file=$2
 
-    # Use sed to remove the header + prepend the jar filename.
-    sed -e '1d' -e "s/^/$jar,/" $file
+    # Remove the header row, and prepend the columns.
+    sed -e '1d' -e "s/^/$jar,$timestamp,/" $file
 }
 
 collect_stats() {
     local out="$1"
     {
         # Copy the header, with the first column appended.
-        echo -n "Jar,"
+        echo -n "Jar,Generated Date,"
         head -n 1 hoststubgen_framework-minus-apex_stats.csv
 
         dump "framework-minus-apex" hoststubgen_framework-minus-apex_stats.csv
@@ -61,7 +69,7 @@
     local out="$1"
     {
         # Copy the header, with the first column appended.
-        echo -n "Jar,"
+        echo -n "Jar,Generated Date,"
         head -n 1 hoststubgen_framework-minus-apex_apis.csv
 
         dump "framework-minus-apex"  hoststubgen_framework-minus-apex_apis.csv
@@ -74,3 +82,7 @@
 
 collect_stats $stats
 collect_apis $apis
+
+cp *keep_all.txt $keep_all_dir
+echo "Keep all files created at:"
+find $keep_all_dir -type f
\ No newline at end of file
diff --git a/ravenwood/texts/framework-minus-apex-ravenwood-policies.txt b/ravenwood/texts/framework-minus-apex-ravenwood-policies.txt
deleted file mode 100644
index 371c3ac..0000000
--- a/ravenwood/texts/framework-minus-apex-ravenwood-policies.txt
+++ /dev/null
@@ -1,59 +0,0 @@
-# Ravenwood "policy" file for framework-minus-apex.
-
-# Keep all AIDL interfaces
-class :aidl stubclass
-
-# Keep all feature flag implementations
-class :feature_flags stubclass
-
-# Keep all sysprops generated code implementations
-class :sysprops stubclass
-
-# Exported to Mainline modules; cannot use annotations
-class com.android.internal.util.FastXmlSerializer stubclass
-class com.android.internal.util.FileRotator stubclass
-class com.android.internal.util.HexDump stubclass
-class com.android.internal.util.IndentingPrintWriter stubclass
-class com.android.internal.util.LocalLog stubclass
-class com.android.internal.util.MessageUtils stubclass
-class com.android.internal.util.TokenBucket stubclass
-class android.os.HandlerExecutor stubclass
-class android.util.BackupUtils stubclass
-class android.util.IndentingPrintWriter stubclass
-class android.util.LocalLog stubclass
-class android.util.Pair stubclass
-class android.util.Rational stubclass
-
-# From modules-utils; cannot use annotations
-class com.android.internal.util.Preconditions stubclass
-class com.android.internal.logging.InstanceId stubclass
-class com.android.internal.logging.InstanceIdSequence stubclass
-class com.android.internal.logging.UiEvent stubclass
-class com.android.internal.logging.UiEventLogger stubclass
-
-# From modules-utils; cannot use annotations
-class com.android.modules.utils.BinaryXmlPullParser stubclass
-class com.android.modules.utils.BinaryXmlSerializer stubclass
-class com.android.modules.utils.FastDataInput stubclass
-class com.android.modules.utils.FastDataOutput stubclass
-class com.android.modules.utils.ModifiedUtf8 stubclass
-class com.android.modules.utils.TypedXmlPullParser stubclass
-class com.android.modules.utils.TypedXmlSerializer stubclass
-
-# Uri
-class android.net.Uri stubclass
-class android.net.UriCodec stubclass
-
-# Telephony
-class android.telephony.PinResult stubclass
-
-# Just enough to support mocking, no further functionality
-class android.content.BroadcastReceiver stub
-    method <init> ()V stub
-class android.content.Context stub
-    method <init> ()V stub
-    method getSystemService (Ljava/lang/Class;)Ljava/lang/Object; stub
-class android.content.pm.PackageManager stub
-    method <init> ()V stub
-class android.text.ClipboardManager stub
-    method <init> ()V stub
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index e452299..f3172ae 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -1,5 +1,7 @@
 # Only classes listed here can use the Ravenwood annotations.
 
+com.android.internal.ravenwood.*
+
 com.android.internal.display.BrightnessSynchronizer
 com.android.internal.util.ArrayUtils
 com.android.internal.logging.MetricsLogger
@@ -237,6 +239,8 @@
 android.accounts.Account
 
 android.graphics.Bitmap$Config
+android.graphics.Color
+android.graphics.ColorSpace
 android.graphics.Insets
 android.graphics.Interpolator
 android.graphics.Matrix
diff --git a/ravenwood/texts/ravenwood-framework-policies.txt b/ravenwood/texts/ravenwood-framework-policies.txt
new file mode 100644
index 0000000..9d29a05
--- /dev/null
+++ b/ravenwood/texts/ravenwood-framework-policies.txt
@@ -0,0 +1,59 @@
+# Ravenwood "policy" file for framework-minus-apex.
+
+# Keep all AIDL interfaces
+class :aidl keepclass
+
+# Keep all feature flag implementations
+class :feature_flags keepclass
+
+# Keep all sysprops generated code implementations
+class :sysprops keepclass
+
+# Exported to Mainline modules; cannot use annotations
+class com.android.internal.util.FastXmlSerializer keepclass
+class com.android.internal.util.FileRotator keepclass
+class com.android.internal.util.HexDump keepclass
+class com.android.internal.util.IndentingPrintWriter keepclass
+class com.android.internal.util.LocalLog keepclass
+class com.android.internal.util.MessageUtils keepclass
+class com.android.internal.util.TokenBucket keepclass
+class android.os.HandlerExecutor keepclass
+class android.util.BackupUtils keepclass
+class android.util.IndentingPrintWriter keepclass
+class android.util.LocalLog keepclass
+class android.util.Pair keepclass
+class android.util.Rational keepclass
+
+# From modules-utils; cannot use annotations
+class com.android.internal.util.Preconditions keepclass
+class com.android.internal.logging.InstanceId keepclass
+class com.android.internal.logging.InstanceIdSequence keepclass
+class com.android.internal.logging.UiEvent keepclass
+class com.android.internal.logging.UiEventLogger keepclass
+
+# From modules-utils; cannot use annotations
+class com.android.modules.utils.BinaryXmlPullParser keepclass
+class com.android.modules.utils.BinaryXmlSerializer keepclass
+class com.android.modules.utils.FastDataInput keepclass
+class com.android.modules.utils.FastDataOutput keepclass
+class com.android.modules.utils.ModifiedUtf8 keepclass
+class com.android.modules.utils.TypedXmlPullParser keepclass
+class com.android.modules.utils.TypedXmlSerializer keepclass
+
+# Uri
+class android.net.Uri keepclass
+class android.net.UriCodec keepclass
+
+# Telephony
+class android.telephony.PinResult keepclass
+
+# Just enough to support mocking, no further functionality
+class android.content.BroadcastReceiver keep
+    method <init> ()V keep
+class android.content.Context keep
+    method <init> ()V keep
+    method getSystemService (Ljava/lang/Class;)Ljava/lang/Object; keep
+class android.content.pm.PackageManager keep
+    method <init> ()V keep
+class android.text.ClipboardManager keep
+    method <init> ()V keep
diff --git a/ravenwood/texts/services.core-ravenwood-policies.txt b/ravenwood/texts/ravenwood-services-policies.txt
similarity index 68%
rename from ravenwood/texts/services.core-ravenwood-policies.txt
rename to ravenwood/texts/ravenwood-services-policies.txt
index d8d563e..5cdb4f7 100644
--- a/ravenwood/texts/services.core-ravenwood-policies.txt
+++ b/ravenwood/texts/ravenwood-services-policies.txt
@@ -1,7 +1,7 @@
 # Ravenwood "policy" file for services.core.
 
 # Keep all AIDL interfaces
-class :aidl stubclass
+class :aidl keepclass
 
 # Keep all feature flag implementations
-class :feature_flags stubclass
+class :feature_flags keepclass
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 1ba47e4..a50fb9a 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -128,6 +128,26 @@
 }
 
 flag {
+    name: "manager_avoid_receiver_timeout"
+    namespace: "accessibility"
+    description: "Register receivers on background handler so they have more time to complete"
+    bug: "333890389"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
+    name: "manager_package_monitor_logic_fix"
+    namespace: "accessibility"
+    description: "Corrects the return values of the HandleForceStop function"
+    bug: "337392123"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "pinch_zoom_zero_min_span"
     namespace: "accessibility"
     description: "Whether to set min span of ScaleGestureDetector to zero."
@@ -142,6 +162,16 @@
 }
 
 flag {
+    name: "remove_on_window_infos_changed_handler"
+    namespace: "accessibility"
+    description: "Updates onWindowInfosChanged() to run without posting to a handler."
+    bug: "333834990"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "reset_hover_event_timer_on_action_up"
     namespace: "accessibility"
     description: "Reset the timer for sending hover events on receiving ACTION_UP to guarantee the correct amount of time is available between taps."
@@ -173,3 +203,10 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "enable_color_correction_saturation"
+    namespace: "accessibility"
+    description: "Feature allows users to change color correction saturation for daltonizer."
+    bug: "322829049"
+}
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 8a699ef..42f168b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -44,10 +44,12 @@
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.accessibilityservice.IBrailleDisplayController;
 import android.accessibilityservice.MagnificationConfig;
+import android.annotation.EnforcePermission;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SuppressLint;
+import android.annotation.PermissionManuallyEnforced;
+import android.annotation.RequiresNoPermission;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
@@ -68,6 +70,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PermissionEnforcer;
 import android.os.PowerManager;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
@@ -121,7 +124,6 @@
  * This class represents an accessibility client - either an AccessibilityService or a UiAutomation.
  * It is responsible for behavior common to both types of clients.
  */
-@SuppressWarnings("MissingPermissionAnnotation")
 abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServiceConnection.Stub
         implements ServiceConnection, IBinder.DeathRecipient, KeyEventDispatcher.KeyEventFilter,
         FingerprintGestureDispatcher.FingerprintGestureClient {
@@ -339,6 +341,7 @@
             AccessibilityTrace trace, WindowManagerInternal windowManagerInternal,
             SystemActionPerformer systemActionPerfomer,
             AccessibilityWindowManager a11yWindowManager) {
+        super(PermissionEnforcer.fromContext(context));
         mContext = context;
         mWindowManagerService = windowManagerInternal;
         mId = id;
@@ -469,6 +472,7 @@
         return (mEventTypes != 0 && mService != null);
     }
 
+    @RequiresNoPermission
     @Override
     public void setOnKeyEventResult(boolean handled, int sequence) {
         if (svcConnTracingEnabled()) {
@@ -482,6 +486,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public AccessibilityServiceInfo getServiceInfo() {
         if (svcConnTracingEnabled()) {
@@ -501,6 +506,7 @@
                 : AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) | mEventTypes;
     }
 
+    @RequiresNoPermission
     @Override
     public void setServiceInfo(AccessibilityServiceInfo info) {
         if (svcConnTracingEnabled()) {
@@ -536,16 +542,19 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public void setInstalledAndEnabledServices(List<AccessibilityServiceInfo> infos) {
         return;
     }
 
+    @RequiresNoPermission
     @Override
     public List<AccessibilityServiceInfo> getInstalledAndEnabledServices() {
         return null;
     }
 
+    @RequiresNoPermission
     @Override
     public void setAttributionTag(String attributionTag) {
         mAttributionTag = attributionTag;
@@ -558,6 +567,7 @@
     protected abstract boolean hasRightsToCurrentUserLocked();
 
     @Nullable
+    @RequiresNoPermission
     @Override
     public AccessibilityWindowInfo.WindowListSparseArray getWindows() {
         if (svcConnTracingEnabled()) {
@@ -606,6 +616,7 @@
         mDisplayTypes = displayTypes;
     }
 
+    @RequiresNoPermission
     @Override
     public AccessibilityWindowInfo getWindow(int windowId) {
         if (svcConnTracingEnabled()) {
@@ -646,6 +657,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public String[] findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
             long accessibilityNodeId, String viewIdResName, int interactionId,
@@ -722,6 +734,7 @@
         return null;
     }
 
+    @RequiresNoPermission
     @Override
     public String[] findAccessibilityNodeInfosByText(int accessibilityWindowId,
             long accessibilityNodeId, String text, int interactionId,
@@ -797,6 +810,7 @@
         return null;
     }
 
+    @RequiresNoPermission
     @Override
     public String[] findAccessibilityNodeInfoByAccessibilityId(
             int accessibilityWindowId, long accessibilityNodeId, int interactionId,
@@ -875,6 +889,7 @@
         return null;
     }
 
+    @RequiresNoPermission
     @Override
     public String[] findFocus(int accessibilityWindowId, long accessibilityNodeId,
             int focusType, int interactionId,
@@ -952,6 +967,7 @@
         return null;
     }
 
+    @RequiresNoPermission
     @Override
     public String[] focusSearch(int accessibilityWindowId, long accessibilityNodeId,
             int direction, int interactionId,
@@ -1028,6 +1044,7 @@
         return null;
     }
 
+    @RequiresNoPermission
     @Override
     public void sendGesture(int sequence, ParceledListSlice gestureSteps) {
         if (svcConnTracingEnabled()) {
@@ -1036,6 +1053,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId) {
         if (svcConnTracingEnabled()) {
@@ -1044,6 +1062,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public boolean performAccessibilityAction(int accessibilityWindowId,
             long accessibilityNodeId, int action, Bundle arguments, int interactionId,
@@ -1075,6 +1094,7 @@
                 action, arguments, interactionId, callback, mFetchFlags, interrogatingTid);
     }
 
+    @RequiresNoPermission
     @Override
     public boolean performGlobalAction(int action) {
         if (svcConnTracingEnabled()) {
@@ -1093,6 +1113,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public @NonNull List<AccessibilityNodeInfo.AccessibilityAction> getSystemActions() {
         if (svcConnTracingEnabled()) {
@@ -1111,6 +1132,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public boolean isFingerprintGestureDetectionAvailable() {
         if (svcConnTracingEnabled()) {
@@ -1133,6 +1155,7 @@
     }
 
     @Nullable
+    @RequiresNoPermission
     @Override
     public MagnificationConfig getMagnificationConfig(int displayId) {
         if (svcConnTracingEnabled()) {
@@ -1151,6 +1174,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public float getMagnificationScale(int displayId) {
         if (svcConnTracingEnabled()) {
@@ -1169,6 +1193,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public Region getMagnificationRegion(int displayId) {
         if (svcConnTracingEnabled()) {
@@ -1193,6 +1218,7 @@
     }
 
 
+    @RequiresNoPermission
     @Override
     public Region getCurrentMagnificationRegion(int displayId) {
         if (svcConnTracingEnabled()) {
@@ -1216,6 +1242,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public float getMagnificationCenterX(int displayId) {
         if (svcConnTracingEnabled()) {
@@ -1237,6 +1264,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public float getMagnificationCenterY(int displayId) {
         if (svcConnTracingEnabled()) {
@@ -1258,6 +1286,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public boolean resetMagnification(int displayId, boolean animate) {
         if (svcConnTracingEnabled()) {
@@ -1282,6 +1311,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public boolean resetCurrentMagnification(int displayId, boolean animate) {
         if (svcConnTracingEnabled()) {
@@ -1307,6 +1337,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public boolean setMagnificationConfig(int displayId,
             @NonNull MagnificationConfig config, boolean animate) {
@@ -1333,6 +1364,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public void setMagnificationCallbackEnabled(int displayId, boolean enabled) {
         if (svcConnTracingEnabled()) {
@@ -1351,6 +1383,7 @@
         return mInvocationHandler.isMagnificationCallbackEnabled(displayId);
     }
 
+    @RequiresNoPermission
     @Override
     public void setSoftKeyboardCallbackEnabled(boolean enabled) {
         if (svcConnTracingEnabled()) {
@@ -1364,6 +1397,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public void takeScreenshotOfWindow(int accessibilityWindowId, int interactionId,
             ScreenCapture.ScreenCaptureListener listener,
@@ -1414,6 +1448,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public void takeScreenshot(int displayId, RemoteCallback callback) {
         if (svcConnTracingEnabled()) {
@@ -1553,6 +1588,7 @@
     }
 
     @Override
+    @PermissionManuallyEnforced
     public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
         synchronized (mLock) {
@@ -1649,6 +1685,7 @@
      * @param displayId The id of the logical display that was added.
      * @return window token.
      */
+    @RequiresNoPermission
     @Override
     public IBinder getOverlayWindowToken(int displayId) {
         if (svcConnTracingEnabled()) {
@@ -1670,6 +1707,7 @@
      * @param token The token
      * @return window id
      */
+    @RequiresNoPermission
     @Override
     public int getWindowIdForLeashToken(@NonNull IBinder token) {
         if (svcConnTracingEnabled()) {
@@ -2523,6 +2561,7 @@
         return mSendMotionEvents;
     }
 
+    @RequiresNoPermission
     @Override
     public void setGestureDetectionPassthroughRegion(int displayId, Region region) {
         if (svcConnTracingEnabled()) {
@@ -2537,6 +2576,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public void setTouchExplorationPassthroughRegion(int displayId, Region region) {
         if (svcConnTracingEnabled()) {
@@ -2551,6 +2591,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public void setFocusAppearance(int strokeWidth, int color) {
         if (svcConnTracingEnabled()) {
@@ -2558,6 +2599,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public void setCacheEnabled(boolean enabled) {
         if (svcConnTracingEnabled()) {
@@ -2574,6 +2616,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public void logTrace(long timestamp, String where, long loggingTypes, String callingParams,
             int processId, long threadId, int callingUid, Bundle callingStack) {
@@ -2629,6 +2672,7 @@
         mTrace.logTrace(TRACE_WM + "." + methodName, FLAGS_WINDOW_MANAGER_INTERNAL, params);
     }
 
+    @RequiresNoPermission
     @Override
     public void setServiceDetectsGesturesEnabled(int displayId, boolean mode) {
         final long identity = Binder.clearCallingIdentity();
@@ -2647,6 +2691,7 @@
         return false;
     }
 
+    @RequiresNoPermission
     @Override
     public void requestTouchExploration(int displayId) {
         final long identity = Binder.clearCallingIdentity();
@@ -2657,6 +2702,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public void requestDragging(int displayId, int pointerId) {
         final long identity = Binder.clearCallingIdentity();
@@ -2667,6 +2713,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public void requestDelegating(int displayId) {
         final long identity = Binder.clearCallingIdentity();
@@ -2677,6 +2724,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public void onDoubleTap(int displayId) {
         final long identity = Binder.clearCallingIdentity();
@@ -2687,6 +2735,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public void onDoubleTapAndHold(int displayId) {
         final long identity = Binder.clearCallingIdentity();
@@ -2700,6 +2749,7 @@
     /**
      * Sets the scaling factor for animations.
      */
+    @RequiresNoPermission
     @Override
     public void setAnimationScale(float scale) {
         final long identity = Binder.clearCallingIdentity();
@@ -2717,6 +2767,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public void attachAccessibilityOverlayToDisplay(
             int interactionId,
@@ -2733,6 +2784,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public void attachAccessibilityOverlayToWindow(
             int interactionId,
@@ -2779,12 +2831,14 @@
     }
 
     @Override
-    @SuppressLint("AndroidFrameworkRequiresPermission") // Unsupported in Abstract class
+    @EnforcePermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void connectBluetoothBrailleDisplay(String bluetoothAddress,
             IBrailleDisplayController controller) {
+        connectBluetoothBrailleDisplay_enforcePermission();
         throw new UnsupportedOperationException();
     }
 
+    @RequiresNoPermission
     @Override
     public void connectUsbBrailleDisplay(UsbDevice usbDevice,
             IBrailleDisplayController controller) {
@@ -2792,8 +2846,9 @@
     }
 
     @Override
-    @SuppressLint("AndroidFrameworkRequiresPermission") // Unsupported in Abstract class
+    @EnforcePermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)
     public void setTestBrailleDisplayData(List<Bundle> brailleDisplays) {
+        setTestBrailleDisplayData_enforcePermission();
         throw new UnsupportedOperationException();
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 726a01c..a15d2ca 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -697,7 +697,7 @@
 
     /**
      * Returns the lock object for any synchronized test blocks.
-     * Should not be used outside of testing.
+     * External classes should only use for testing.
      * @return lock object.
      */
     @VisibleForTesting
@@ -801,7 +801,7 @@
      *
      * @param packages list of packages that have stopped.
      * @param userState user state to be read & modified.
-     * @return {@code true} if a service was enabled or a button target was removed,
+     * @return {@code true} if the lists of enabled services or buttons were changed,
      * {@code false} otherwise.
      */
     @VisibleForTesting
@@ -824,6 +824,7 @@
                     userState.getBindingServicesLocked().remove(comp);
                     userState.getCrashedServicesLocked().remove(comp);
                     enabledServicesChanged = true;
+                    break;
                 }
             }
         }
@@ -851,132 +852,14 @@
         return mPackageMonitor;
     }
 
+    @VisibleForTesting
+    void setPackageMonitor(PackageMonitor monitor) {
+        mPackageMonitor = monitor;
+    }
+
     private void registerBroadcastReceivers() {
-        mPackageMonitor = new PackageMonitor(true) {
-            @Override
-            public void onSomePackagesChanged() {
-                if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
-                    mTraceManager.logTrace(LOG_TAG + ".PM.onSomePackagesChanged",
-                            FLAGS_PACKAGE_BROADCAST_RECEIVER);
-                }
-
-                final int userId = getChangingUserId();
-                List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
-                List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
-                parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
-                parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
-                synchronized (mLock) {
-                    // Only the profile parent can install accessibility services.
-                    // Therefore we ignore packages from linked profiles.
-                    if (userId != mCurrentUserId) {
-                        return;
-                    }
-                    onSomePackagesChangedLocked(parsedAccessibilityServiceInfos,
-                            parsedAccessibilityShortcutInfos);
-                }
-            }
-
-            @Override
-            public void onPackageUpdateFinished(String packageName, int uid) {
-                // The package should already be removed from mBoundServices, and added into
-                // mBindingServices in binderDied() during updating. Remove services from  this
-                // package from mBindingServices, and then update the user state to re-bind new
-                // versions of them.
-                if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
-                    mTraceManager.logTrace(LOG_TAG + ".PM.onPackageUpdateFinished",
-                            FLAGS_PACKAGE_BROADCAST_RECEIVER,
-                            "packageName=" + packageName + ";uid=" + uid);
-                }
-                final int userId = getChangingUserId();
-                List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
-                List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
-                parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
-                parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
-                synchronized (mLock) {
-                    if (userId != mCurrentUserId) {
-                        return;
-                    }
-                    final AccessibilityUserState userState = getUserStateLocked(userId);
-                    final boolean reboundAService = userState.getBindingServicesLocked().removeIf(
-                            component -> component != null
-                                    && component.getPackageName().equals(packageName))
-                            || userState.mCrashedServices.removeIf(component -> component != null
-                                    && component.getPackageName().equals(packageName));
-                    // Reloads the installed services info to make sure the rebound service could
-                    // get a new one.
-                    userState.mInstalledServices.clear();
-                    final boolean configurationChanged;
-                    configurationChanged = readConfigurationForUserStateLocked(userState,
-                            parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos);
-                    if (reboundAService || configurationChanged) {
-                        onUserStateChangedLocked(userState);
-                    }
-                    // Passing 0 for restoreFromSdkInt to have this migration check execute each
-                    // time. It can make sure a11y button settings are correctly if there's an a11y
-                    // service updated and modifies the a11y button configuration.
-                    migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, packageName,
-                            /* restoreFromSdkInt = */0);
-                }
-            }
-
-            @Override
-            public void onPackageRemoved(String packageName, int uid) {
-                if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
-                    mTraceManager.logTrace(LOG_TAG + ".PM.onPackageRemoved",
-                            FLAGS_PACKAGE_BROADCAST_RECEIVER,
-                            "packageName=" + packageName + ";uid=" + uid);
-                }
-
-                synchronized (mLock) {
-                    final int userId = getChangingUserId();
-                    // Only the profile parent can install accessibility services.
-                    // Therefore we ignore packages from linked profiles.
-                    if (userId != mCurrentUserId) {
-                        return;
-                    }
-                    onPackageRemovedLocked(packageName);
-                }
-            }
-
-            /**
-             * Handles instances in which a package or packages have forcibly stopped.
-             *
-             * @param intent intent containing package event information.
-             * @param uid linux process user id (different from Android user id).
-             * @param packages array of package names that have stopped.
-             * @param doit whether to try and handle the stop or just log the trace.
-             *
-             * @return {@code true} if package should be restarted, {@code false} otherwise.
-             */
-            @Override
-            public boolean onHandleForceStop(Intent intent, String[] packages,
-                    int uid, boolean doit) {
-                if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
-                    mTraceManager.logTrace(LOG_TAG + ".PM.onHandleForceStop",
-                            FLAGS_PACKAGE_BROADCAST_RECEIVER,
-                            "intent=" + intent + ";packages=" + Arrays.toString(packages)
-                            + ";uid=" + uid + ";doit=" + doit);
-                }
-                synchronized (mLock) {
-                    final int userId = getChangingUserId();
-                    // Only the profile parent can install accessibility services.
-                    // Therefore we ignore packages from linked profiles.
-                    if (userId != mCurrentUserId) {
-                        return false;
-                    }
-                    final AccessibilityUserState userState = getUserStateLocked(userId);
-
-                    if (doit && onPackagesForceStoppedLocked(packages, userState)) {
-                        onUserStateChangedLocked(userState);
-                        return false;
-                    } else {
-                        return true;
-                    }
-                }
-            }
-        };
-
         // package changes
+        mPackageMonitor = new ManagerPackageMonitor(this);
         mPackageMonitor.register(mContext, null,  UserHandle.ALL, true);
 
         // user change and unlock
@@ -986,11 +869,15 @@
         intentFilter.addAction(Intent.ACTION_USER_REMOVED);
         intentFilter.addAction(Intent.ACTION_SETTING_RESTORED);
 
+        Handler receiverHandler =
+                Flags.managerAvoidReceiverTimeout() ? BackgroundThread.getHandler() : null;
         mContext.registerReceiverAsUser(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
                 if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_USER_BROADCAST_RECEIVER)) {
-                    mTraceManager.logTrace(LOG_TAG + ".BR.onReceive", FLAGS_USER_BROADCAST_RECEIVER,
+                    mTraceManager.logTrace(
+                            LOG_TAG + ".BR.onReceive",
+                            FLAGS_USER_BROADCAST_RECEIVER,
                             "context=" + context + ";intent=" + intent);
                 }
 
@@ -1033,7 +920,7 @@
                     }
                 }
             }
-        }, UserHandle.ALL, intentFilter, null, null);
+        }, UserHandle.ALL, intentFilter, null, receiverHandler);
 
         final IntentFilter filter = new IntentFilter();
         filter.addAction(SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED);
@@ -1043,7 +930,8 @@
                 setNonA11yToolNotificationToMatchSafetyCenter();
             }
         };
-        mContext.registerReceiverAsUser(receiver, UserHandle.ALL, filter, null, mMainHandler,
+        mContext.registerReceiverAsUser(
+                receiver, UserHandle.ALL, filter, null, mMainHandler,
                 Context.RECEIVER_EXPORTED);
 
         if (!android.companion.virtual.flags.Flags.vdmPublicApis()) {
@@ -4369,7 +4257,7 @@
                 );
 
         if (!targetWithNoTile.isEmpty()) {
-            throw new IllegalArgumentException(
+            Slog.e(LOG_TAG,
                     "Unable to add/remove Tiles for a11y features: " + targetWithNoTile
                             + "as the Tiles aren't provided");
         }
@@ -6221,6 +6109,162 @@
         }
     }
 
+    @VisibleForTesting
+    public static class ManagerPackageMonitor extends PackageMonitor {
+        private final AccessibilityManagerService mManagerService;
+        public ManagerPackageMonitor(AccessibilityManagerService managerService) {
+            super(/* supportsPackageRestartQuery = */ true);
+            mManagerService = managerService;
+        }
+
+        @Override
+        public void onSomePackagesChanged() {
+            if (mManagerService.mTraceManager.isA11yTracingEnabledForTypes(
+                    FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
+                mManagerService.mTraceManager.logTrace(LOG_TAG + ".PM.onSomePackagesChanged",
+                        FLAGS_PACKAGE_BROADCAST_RECEIVER);
+            }
+
+            final int userId = getChangingUserId();
+            List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = mManagerService
+                    .parseAccessibilityServiceInfos(userId);
+            List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = mManagerService
+                    .parseAccessibilityShortcutInfos(userId);
+            synchronized (mManagerService.getLock()) {
+                // Only the profile parent can install accessibility services.
+                // Therefore we ignore packages from linked profiles.
+                if (userId != mManagerService.getCurrentUserIdLocked()) {
+                    return;
+                }
+                mManagerService.onSomePackagesChangedLocked(parsedAccessibilityServiceInfos,
+                        parsedAccessibilityShortcutInfos);
+            }
+        }
+
+        @Override
+        public void onPackageUpdateFinished(String packageName, int uid) {
+            // The package should already be removed from mBoundServices, and added into
+            // mBindingServices in binderDied() during updating. Remove services from  this
+            // package from mBindingServices, and then update the user state to re-bind new
+            // versions of them.
+            if (mManagerService.mTraceManager.isA11yTracingEnabledForTypes(
+                    FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
+                mManagerService.mTraceManager.logTrace(
+                        LOG_TAG + ".PM.onPackageUpdateFinished",
+                        FLAGS_PACKAGE_BROADCAST_RECEIVER,
+                        "packageName=" + packageName + ";uid=" + uid);
+            }
+            final int userId = getChangingUserId();
+            List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = mManagerService
+                    .parseAccessibilityServiceInfos(userId);
+            List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos =
+                    mManagerService.parseAccessibilityShortcutInfos(userId);
+            synchronized (mManagerService.getLock()) {
+                if (userId != mManagerService.getCurrentUserIdLocked()) {
+                    return;
+                }
+                final AccessibilityUserState userState = mManagerService.getUserStateLocked(userId);
+                final boolean reboundAService = userState.getBindingServicesLocked().removeIf(
+                        component -> component != null
+                                && component.getPackageName().equals(packageName))
+                        || userState.mCrashedServices.removeIf(component -> component != null
+                        && component.getPackageName().equals(packageName));
+                // Reloads the installed services info to make sure the rebound service could
+                // get a new one.
+                userState.mInstalledServices.clear();
+                final boolean configurationChanged;
+                configurationChanged = mManagerService.readConfigurationForUserStateLocked(
+                        userState, parsedAccessibilityServiceInfos,
+                        parsedAccessibilityShortcutInfos);
+                if (reboundAService || configurationChanged) {
+                    mManagerService.onUserStateChangedLocked(userState);
+                }
+                // Passing 0 for restoreFromSdkInt to have this migration check execute each
+                // time. It can make sure a11y button settings are correctly if there's an a11y
+                // service updated and modifies the a11y button configuration.
+                mManagerService.migrateAccessibilityButtonSettingsIfNecessaryLocked(
+                        userState, packageName, /* restoreFromSdkInt = */0);
+            }
+        }
+
+        @Override
+        public void onPackageRemoved(String packageName, int uid) {
+            if (mManagerService.mTraceManager.isA11yTracingEnabledForTypes(
+                    FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
+                mManagerService.mTraceManager.logTrace(LOG_TAG + ".PM.onPackageRemoved",
+                        FLAGS_PACKAGE_BROADCAST_RECEIVER,
+                        "packageName=" + packageName + ";uid=" + uid);
+            }
+
+            synchronized (mManagerService.getLock()) {
+                final int userId = getChangingUserId();
+                // Only the profile parent can install accessibility services.
+                // Therefore we ignore packages from linked profiles.
+                if (userId != mManagerService.getCurrentUserIdLocked()) {
+                    return;
+                }
+                mManagerService.onPackageRemovedLocked(packageName);
+            }
+        }
+
+        /**
+         * Handles instances in which a package or packages have forcibly stopped.
+         *
+         * @param intent intent containing package event information.
+         * @param uid linux process user id (different from Android user id).
+         * @param packages array of package names that have stopped.
+         * @param doit whether to try and handle the stop or just log the trace.
+         *
+         * @return {@code true} if doit == {@code false}
+         * and at least one of the provided packages is enabled.
+         * In any other case, returns {@code false}.
+         * This is to indicate whether further action is necessary.
+         */
+        @Override
+        public boolean onHandleForceStop(Intent intent, String[] packages,
+                int uid, boolean doit) {
+            if (mManagerService.mTraceManager.isA11yTracingEnabledForTypes(
+                    FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
+                mManagerService.mTraceManager.logTrace(LOG_TAG + ".PM.onHandleForceStop",
+                        FLAGS_PACKAGE_BROADCAST_RECEIVER,
+                        "intent=" + intent + ";packages=" + Arrays.toString(packages)
+                                + ";uid=" + uid + ";doit=" + doit);
+            }
+            synchronized (mManagerService.getLock()) {
+                final int userId = getChangingUserId();
+                // Only the profile parent can install accessibility services.
+                // Therefore we ignore packages from linked profiles.
+                if (userId != mManagerService.getCurrentUserIdLocked()) {
+                    return false;
+                }
+                final AccessibilityUserState userState = mManagerService.getUserStateLocked(userId);
+
+                if (Flags.managerPackageMonitorLogicFix()) {
+                    if (!doit) {
+                        // if we're not handling the stop here, then we only need to know
+                        // if any of the force-stopped packages are currently enabled.
+                        return userState.mEnabledServices.stream().anyMatch(
+                                (comp) -> Arrays.stream(packages).anyMatch(
+                                        (pkg) -> pkg.equals(comp.getPackageName()))
+                        );
+                    } else if (mManagerService.onPackagesForceStoppedLocked(packages, userState)) {
+                        mManagerService.onUserStateChangedLocked(userState);
+                    }
+                    return false;
+                } else {
+                    // this old logic did not properly indicate when base packageMonitor's routine
+                    // should handle stopping the package.
+                    if (doit && mManagerService.onPackagesForceStoppedLocked(packages, userState)) {
+                        mManagerService.onUserStateChangedLocked(userState);
+                        return false;
+                    } else {
+                        return true;
+                    }
+                }
+            }
+        }
+    }
+
     void sendPendingWindowStateChangedEventsForAvailableWindowLocked(int windowId) {
         final int eventSize =  mSendWindowStateChangedEventRunnables.size();
         for (int i = eventSize - 1; i >= 0; i--) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 1f65e15..786d167 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -30,10 +30,10 @@
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.accessibilityservice.IBrailleDisplayController;
 import android.accessibilityservice.TouchInteractionController;
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
-import android.annotation.SuppressLint;
+import android.annotation.RequiresNoPermission;
 import android.annotation.UserIdInt;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
@@ -79,7 +79,6 @@
  * passed to the service it represents as soon it is bound. It also serves as the
  * connection for the service.
  */
-@SuppressWarnings("MissingPermissionAnnotation")
 class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnection {
     private static final String LOG_TAG = "AccessibilityServiceConnection";
 
@@ -110,6 +109,7 @@
             mUserId = userId;
         }
 
+        @RequiresNoPermission
         @Override
         public void sessionCreated(IAccessibilityInputMethodSession session, int id) {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "ASC.sessionCreated");
@@ -196,6 +196,7 @@
         return mSecurityPolicy.canRetrieveWindowContentLocked(this) && mRetrieveInteractiveWindows;
     }
 
+    @RequiresNoPermission
     @Override
     public void disableSelf() {
         if (svcConnTracingEnabled()) {
@@ -253,6 +254,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public AccessibilityServiceInfo getServiceInfo() {
         return mAccessibilityServiceInfo;
@@ -330,6 +332,7 @@
         return false;
     }
 
+    @RequiresNoPermission
     @Override
     public boolean setSoftKeyboardShowMode(int showMode) {
         if (svcConnTracingEnabled()) {
@@ -351,6 +354,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public int getSoftKeyboardShowMode() {
         if (svcConnTracingEnabled()) {
@@ -365,6 +369,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public boolean switchToInputMethod(String imeId) {
         if (svcConnTracingEnabled()) {
@@ -386,6 +391,7 @@
         return result;
     }
 
+    @RequiresNoPermission
     @Override
     @AccessibilityService.SoftKeyboardController.EnableImeResult
     public int setInputMethodEnabled(String imeId, boolean enabled) throws SecurityException {
@@ -421,6 +427,7 @@
         return ENABLE_IME_FAIL_UNKNOWN;
     }
 
+    @RequiresNoPermission
     @Override
     public boolean isAccessibilityButtonAvailable() {
         if (svcConnTracingEnabled()) {
@@ -535,6 +542,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId) {
         synchronized (mLock) {
@@ -569,6 +577,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public void setFocusAppearance(int strokeWidth, int color) {
         AccessibilityUserState userState = mUserStateWeakReference.get();
@@ -683,16 +692,15 @@
      *                         {@link android.bluetooth.BluetoothDevice#getAddress()}.
      */
     @Override
-    @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
+    @EnforcePermission(Manifest.permission.BLUETOOTH_CONNECT)
     public void connectBluetoothBrailleDisplay(
             @NonNull String bluetoothAddress, @NonNull IBrailleDisplayController controller) {
+        connectBluetoothBrailleDisplay_enforcePermission();
         if (!android.view.accessibility.Flags.brailleDisplayHid()) {
             throw new IllegalStateException("Flag BRAILLE_DISPLAY_HID not enabled");
         }
         Objects.requireNonNull(bluetoothAddress);
         Objects.requireNonNull(controller);
-        mContext.enforceCallingPermission(Manifest.permission.BLUETOOTH_CONNECT,
-                "Missing BLUETOOTH_CONNECT permission");
         if (!BluetoothAdapter.checkBluetoothAddress(bluetoothAddress)) {
             throw new IllegalArgumentException(
                     bluetoothAddress + " is not a valid Bluetooth address");
@@ -728,7 +736,7 @@
      *
      * <p>The caller package must already have USB permission for this {@link UsbDevice}.
      */
-    @SuppressLint("MissingPermission") // system_server has the required MANAGE_USB permission
+    @RequiresNoPermission
     @Override
     @NonNull
     public void connectUsbBrailleDisplay(@NonNull UsbDevice usbDevice,
@@ -783,11 +791,10 @@
     }
 
     @Override
-    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+    @EnforcePermission(Manifest.permission.MANAGE_ACCESSIBILITY)
     public void setTestBrailleDisplayData(List<Bundle> brailleDisplays) {
+        setTestBrailleDisplayData_enforcePermission();
         // Enforce that this TestApi is only called by trusted (test) callers.
-        mContext.enforceCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
-                "Missing MANAGE_ACCESSIBILITY permission");
         mTestBrailleDisplays = brailleDisplays;
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index b77b2be..4cb3d24 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -24,6 +24,8 @@
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.accessibilityservice.MagnificationConfig;
 import android.annotation.NonNull;
+import android.annotation.PermissionManuallyEnforced;
+import android.annotation.RequiresNoPermission;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -64,7 +66,6 @@
  *
  * TODO(241429275): Initialize this when a proxy is registered.
  */
-@SuppressWarnings("MissingPermissionAnnotation")
 public class ProxyAccessibilityServiceConnection extends AccessibilityServiceConnection {
     private static final String LOG_TAG = "ProxyAccessibilityServiceConnection";
 
@@ -125,6 +126,7 @@
      *
      * @param infos the list of enabled and installed services.
      */
+    @RequiresNoPermission
     @Override
     public void setInstalledAndEnabledServices(@NonNull List<AccessibilityServiceInfo> infos) {
         final long identity = Binder.clearCallingIdentity();
@@ -215,6 +217,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     @NonNull
     public List<AccessibilityServiceInfo> getInstalledAndEnabledServices() {
@@ -224,6 +227,7 @@
         }
     }
 
+    @RequiresNoPermission
     @Override
     public AccessibilityWindowInfo.WindowListSparseArray getWindows() {
         final AccessibilityWindowInfo.WindowListSparseArray allWindows = super.getWindows();
@@ -235,6 +239,7 @@
         return displayWindows;
     }
 
+    @RequiresNoPermission
     @Override
     public void setFocusAppearance(int strokeWidth, int color) {
         synchronized (mLock) {
@@ -272,6 +277,7 @@
         return mFocusColor;
     }
 
+    @RequiresNoPermission
     @Override
     int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) {
         if (windowId == AccessibilityWindowInfo.ANY_WINDOW_ID) {
@@ -314,12 +320,14 @@
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need fingerprint hardware */
+    @RequiresNoPermission
     @Override
     public boolean isCapturingFingerprintGestures() throws UnsupportedOperationException {
         throw new UnsupportedOperationException("isCapturingFingerprintGestures is not supported");
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need fingerprint hardware */
+    @RequiresNoPermission
     @Override
     public void onFingerprintGestureDetectionActiveChanged(boolean active)
             throws UnsupportedOperationException {
@@ -328,12 +336,14 @@
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need fingerprint hardware */
+    @RequiresNoPermission
     @Override
     public void onFingerprintGesture(int gesture) throws UnsupportedOperationException {
         throw new UnsupportedOperationException("onFingerprintGesture is not supported");
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need fingerprint hardware */
+    @RequiresNoPermission
     @Override
     public boolean isFingerprintGestureDetectionAvailable() throws UnsupportedOperationException {
         throw new UnsupportedOperationException("isFingerprintGestureDetectionAvailable is not"
@@ -341,6 +351,7 @@
     }
 
     /** @throws UnsupportedOperationException since a proxy is not a Service */
+    @RequiresNoPermission
     @Override
     public void onServiceConnected(ComponentName name, IBinder service)
             throws UnsupportedOperationException {
@@ -349,6 +360,7 @@
     }
 
     /** @throws UnsupportedOperationException since a proxy is not a Service */
+    @RequiresNoPermission
     @Override
     public void onServiceDisconnected(ComponentName name)
             throws UnsupportedOperationException {
@@ -357,6 +369,7 @@
 
     /** @throws UnsupportedOperationException since a proxy should use
      * setInstalledAndEnabledServices*/
+    @RequiresNoPermission
     @Override
     public void setServiceInfo(AccessibilityServiceInfo info)
             throws UnsupportedOperationException  {
@@ -365,6 +378,7 @@
     }
 
     /** @throws UnsupportedOperationException since a proxy should use A11yManager#unregister */
+    @RequiresNoPermission
     @Override
     public void disableSelf() throws UnsupportedOperationException {
         // A proxy uses A11yManager#unregister to turn itself off.
@@ -372,12 +386,14 @@
     }
 
     /** @throws UnsupportedOperationException since a proxy does not have global system access */
+    @RequiresNoPermission
     @Override
     public boolean performGlobalAction(int action) throws UnsupportedOperationException {
         throw new UnsupportedOperationException("performGlobalAction is not supported");
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need key events */
+    @RequiresNoPermission
     @Override
     public void setOnKeyEventResult(boolean handled, int sequence)
             throws UnsupportedOperationException {
@@ -385,6 +401,7 @@
     }
 
     /** @throws UnsupportedOperationException since a proxy does not have global system access */
+    @RequiresNoPermission
     @Override
     public @NonNull List<AccessibilityNodeInfo.AccessibilityAction> getSystemActions()
             throws UnsupportedOperationException {
@@ -393,6 +410,7 @@
 
     /** @throws UnsupportedOperationException since a proxy does not need magnification */
     @Nullable
+    @RequiresNoPermission
     @Override
     public MagnificationConfig getMagnificationConfig(int displayId)
             throws UnsupportedOperationException {
@@ -400,30 +418,35 @@
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need magnification */
+    @RequiresNoPermission
     @Override
     public float getMagnificationScale(int displayId) throws UnsupportedOperationException {
         throw new UnsupportedOperationException("getMagnificationScale is not supported");
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need magnification */
+    @RequiresNoPermission
     @Override
     public float getMagnificationCenterX(int displayId) throws UnsupportedOperationException {
         throw new UnsupportedOperationException("getMagnificationCenterX is not supported");
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need magnification */
+    @RequiresNoPermission
     @Override
     public float getMagnificationCenterY(int displayId) throws UnsupportedOperationException {
         throw new UnsupportedOperationException("getMagnificationCenterY is not supported");
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need magnification */
+    @RequiresNoPermission
     @Override
     public Region getMagnificationRegion(int displayId) throws UnsupportedOperationException {
         throw new UnsupportedOperationException("getMagnificationRegion is not supported");
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need magnification */
+    @RequiresNoPermission
     @Override
     public Region getCurrentMagnificationRegion(int displayId)
             throws UnsupportedOperationException {
@@ -431,6 +454,7 @@
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need magnification */
+    @RequiresNoPermission
     @Override
     public boolean resetMagnification(int displayId, boolean animate)
             throws UnsupportedOperationException {
@@ -438,6 +462,7 @@
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need magnification */
+    @RequiresNoPermission
     @Override
     public boolean resetCurrentMagnification(int displayId, boolean animate)
             throws UnsupportedOperationException {
@@ -445,6 +470,7 @@
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need magnification */
+    @RequiresNoPermission
     @Override
     public boolean setMagnificationConfig(int displayId,
             @androidx.annotation.NonNull MagnificationConfig config, boolean animate)
@@ -453,6 +479,7 @@
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need magnification */
+    @RequiresNoPermission
     @Override
     public void setMagnificationCallbackEnabled(int displayId, boolean enabled)
             throws UnsupportedOperationException {
@@ -460,24 +487,28 @@
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need magnification */
+    @RequiresNoPermission
     @Override
     public boolean isMagnificationCallbackEnabled(int displayId) {
         throw new UnsupportedOperationException("isMagnificationCallbackEnabled is not supported");
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need IME access*/
+    @RequiresNoPermission
     @Override
     public boolean setSoftKeyboardShowMode(int showMode) throws UnsupportedOperationException {
         throw new UnsupportedOperationException("setSoftKeyboardShowMode is not supported");
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need IME access */
+    @RequiresNoPermission
     @Override
     public int getSoftKeyboardShowMode() throws UnsupportedOperationException {
         throw new UnsupportedOperationException("getSoftKeyboardShowMode is not supported");
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need IME access */
+    @RequiresNoPermission
     @Override
     public void setSoftKeyboardCallbackEnabled(boolean enabled)
             throws UnsupportedOperationException {
@@ -485,12 +516,14 @@
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need IME access */
+    @RequiresNoPermission
     @Override
     public boolean switchToInputMethod(String imeId) throws UnsupportedOperationException {
         throw new UnsupportedOperationException("switchToInputMethod is not supported");
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need IME access */
+    @RequiresNoPermission
     @Override
     public int setInputMethodEnabled(String imeId, boolean enabled)
             throws UnsupportedOperationException {
@@ -498,12 +531,14 @@
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need access to the shortcut */
+    @RequiresNoPermission
     @Override
     public boolean isAccessibilityButtonAvailable() throws UnsupportedOperationException {
         throw new UnsupportedOperationException("isAccessibilityButtonAvailable is not supported");
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need gestures/input access */
+    @RequiresNoPermission
     @Override
     public void sendGesture(int sequence, ParceledListSlice gestureSteps)
             throws UnsupportedOperationException {
@@ -511,6 +546,7 @@
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need gestures/input access */
+    @RequiresNoPermission
     @Override
     public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId)
             throws UnsupportedOperationException {
@@ -518,6 +554,7 @@
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need access to screenshots */
+    @RequiresNoPermission
     @Override
     public void takeScreenshot(int displayId, RemoteCallback callback)
             throws UnsupportedOperationException {
@@ -525,6 +562,7 @@
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need gestures/input access */
+    @RequiresNoPermission
     @Override
     public void setGestureDetectionPassthroughRegion(int displayId, Region region)
             throws UnsupportedOperationException {
@@ -533,6 +571,7 @@
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need gestures/input access */
+    @RequiresNoPermission
     @Override
     public void setTouchExplorationPassthroughRegion(int displayId, Region region)
             throws UnsupportedOperationException {
@@ -541,6 +580,7 @@
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need gestures/input access */
+    @RequiresNoPermission
     @Override
     public void setServiceDetectsGesturesEnabled(int displayId, boolean mode)
             throws UnsupportedOperationException {
@@ -549,36 +589,42 @@
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need touch input access */
+    @RequiresNoPermission
     @Override
     public void requestTouchExploration(int displayId) throws UnsupportedOperationException  {
         throw new UnsupportedOperationException("requestTouchExploration is not supported");
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need touch input access */
+    @RequiresNoPermission
     @Override
     public void requestDragging(int displayId, int pointerId) throws UnsupportedOperationException {
         throw new UnsupportedOperationException("requestDragging is not supported");
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need touch input access */
+    @RequiresNoPermission
     @Override
     public void requestDelegating(int displayId) throws UnsupportedOperationException {
         throw new UnsupportedOperationException("requestDelegating is not supported");
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need touch input access */
+    @RequiresNoPermission
     @Override
     public void onDoubleTap(int displayId) throws UnsupportedOperationException {
         throw new UnsupportedOperationException("onDoubleTap is not supported");
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need touch input access */
+    @RequiresNoPermission
     @Override
     public void onDoubleTapAndHold(int displayId) throws UnsupportedOperationException {
         throw new UnsupportedOperationException("onDoubleTapAndHold is not supported");
     }
 
     /** @throws UnsupportedOperationException since a proxy does not need touch input access */
+    @RequiresNoPermission
     @Override
     public void setAnimationScale(float scale) throws UnsupportedOperationException {
         throw new UnsupportedOperationException("setAnimationScale is not supported");
@@ -615,6 +661,7 @@
         return updated;
     }
 
+    @PermissionManuallyEnforced
     @Override
     public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index aad9e24..63a183d 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -21,6 +21,8 @@
 import android.accessibilityservice.AccessibilityTrace;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.annotation.Nullable;
+import android.annotation.PermissionManuallyEnforced;
+import android.annotation.RequiresNoPermission;
 import android.app.UiAutomation;
 import android.content.ComponentName;
 import android.content.Context;
@@ -242,7 +244,6 @@
         }
     }
 
-    @SuppressWarnings("MissingPermissionAnnotation")
     private class UiAutomationService extends AbstractAccessibilityServiceConnection {
         private final Handler mMainHandler;
 
@@ -318,6 +319,7 @@
             return true;
         }
 
+        @PermissionManuallyEnforced
         @Override
         public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
@@ -330,31 +332,37 @@
         }
 
         // Since this isn't really an accessibility service, several methods are just stubbed here.
+        @RequiresNoPermission
         @Override
         public boolean setSoftKeyboardShowMode(int mode) {
             return false;
         }
 
+        @RequiresNoPermission
         @Override
         public int getSoftKeyboardShowMode() {
             return 0;
         }
 
+        @RequiresNoPermission
         @Override
         public boolean switchToInputMethod(String imeId) {
             return false;
         }
 
+        @RequiresNoPermission
         @Override
         public int setInputMethodEnabled(String imeId, boolean enabled) {
             return AccessibilityService.SoftKeyboardController.ENABLE_IME_FAIL_UNKNOWN;
         }
 
+        @RequiresNoPermission
         @Override
         public boolean isAccessibilityButtonAvailable() {
             return false;
         }
 
+        @RequiresNoPermission
         @Override
         public void disableSelf() {}
 
@@ -375,6 +383,7 @@
         @Override
         public void onFingerprintGesture(int gesture) {}
 
+        @RequiresNoPermission
         @Override
         public void takeScreenshot(int displayId, RemoteCallback callback) {}
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index 2f54f8c..2a7458f 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -1469,10 +1469,7 @@
             int policyFlags = mState.getLastReceivedPolicyFlags();
             if (mState.isDragging()) {
                 // Send an event to the end of the drag gesture.
-                int pointerIdBits = ALL_POINTER_ID_BITS;
-                if (Flags.fixDragPointerWhenEndingDrag()) {
-                    pointerIdBits = 1 << mDraggingPointerId;
-                }
+                int pointerIdBits = 1 << mDraggingPointerId;
                 mDispatcher.sendMotionEvent(event, ACTION_UP, rawEvent, pointerIdBits, policyFlags);
             }
             mState.startDelegating();
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index e830523..5567707 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -83,6 +83,7 @@
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
@@ -171,7 +172,7 @@
         OnCrossProfileWidgetProvidersChangeListener {
     private static final String TAG = "AppWidgetServiceImpl";
 
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = Build.IS_DEBUGGABLE;
 
     private static final String OLD_KEYGUARD_HOST_PACKAGE = "android";
     private static final String NEW_KEYGUARD_HOST_PACKAGE = "com.android.keyguard";
@@ -520,9 +521,13 @@
                         false);
                 final boolean packageRemovedPermanently =
                         (extras == null || !isReplacing || (isReplacing && isArchival));
-
                 if (packageRemovedPermanently) {
                     for (String pkgName : pkgList) {
+                        if (DEBUG) {
+                            Slog.i(TAG, "calling removeHostsAndProvidersForPackageLocked() "
+                                    + "because package removed permanently. extras=" + extras
+                                    + " isReplacing=" + isReplacing + " isArchival=" + isArchival);
+                        }
                         componentsModified |= removeHostsAndProvidersForPackageLocked(
                                 pkgName, userId);
                     }
@@ -736,7 +741,10 @@
         }
         RemoteViews views = new RemoteViews(mContext.getPackageName(),
                 R.layout.work_widget_mask_view);
-        ApplicationInfo appInfo = provider.info.providerInfo.applicationInfo;
+        final ActivityInfo activityInfo = provider.info.providerInfo;
+        final ApplicationInfo appInfo = activityInfo != null ? activityInfo.applicationInfo : null;
+        final String packageName = appInfo != null
+                ? appInfo.packageName : provider.id.componentName.getPackageName();
         final int appUserId = provider.getUserId();
         boolean showBadge = false;
 
@@ -750,7 +758,7 @@
             } else if (provider.maskedBySuspendedPackage) {
                 showBadge = mUserManager.hasBadge(appUserId);
                 final UserPackage suspendingPackage = mPackageManagerInternal.getSuspendingPackage(
-                        appInfo.packageName, appUserId);
+                        packageName, appUserId);
                 // TODO(b/281839596): don't rely on platform always meaning suspended by admin.
                 if (suspendingPackage != null
                         && PLATFORM_PACKAGE_NAME.equals(suspendingPackage.packageName)) {
@@ -759,11 +767,11 @@
                 } else {
                     final SuspendDialogInfo dialogInfo =
                             mPackageManagerInternal.getSuspendedDialogInfo(
-                                    appInfo.packageName, suspendingPackage, appUserId);
+                                    packageName, suspendingPackage, appUserId);
                     // onUnsuspend is null because we don't want to start any activity on
                     // unsuspending from a suspended widget.
                     onClickIntent = SuspendedAppActivity.createSuspendedAppInterceptIntent(
-                            appInfo.packageName, suspendingPackage, dialogInfo, null, null,
+                            packageName, suspendingPackage, dialogInfo, null, null,
                             appUserId);
                 }
             } else if (provider.maskedByLockedProfile) {
@@ -778,7 +786,7 @@
                 showBadge = mUserManager.hasBadge(appUserId);
             }
 
-            Icon icon = appInfo.icon != 0
+            Icon icon = (appInfo != null && appInfo.icon != 0)
                     ? Icon.createWithResource(appInfo.packageName, appInfo.icon)
                     : Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
             views.setImageViewIcon(R.id.work_widget_app_icon, icon);
@@ -1568,9 +1576,36 @@
                     Binder.getCallingUid(), callingPackage);
 
             if (widget != null && widget.provider != null && !widget.provider.zombie) {
-                return cloneIfLocalBinder(widget.provider.getInfoLocked(mContext));
+                final AppWidgetProviderInfo info = widget.provider.getInfoLocked(mContext);
+                if (info == null) {
+                    Slog.e(TAG, "getAppWidgetInfo() returns null because"
+                            + " widget.provider.getInfoLocked() returned null."
+                            + " appWidgetId=" + appWidgetId + " userId=" + userId
+                            + " widget=" + widget);
+                    return null;
+                }
+                final AppWidgetProviderInfo ret = cloneIfLocalBinder(info);
+                if (ret == null) {
+                    Slog.e(TAG, "getAppWidgetInfo() returns null because"
+                            + " cloneIfLocalBinder() returned null."
+                            + " appWidgetId=" + appWidgetId + " userId=" + userId
+                            + " widget=" + widget + " appWidgetProviderInfo=" + info);
+                }
+                return ret;
+            } else {
+                if (widget == null) {
+                    Slog.e(TAG, "getAppWidgetInfo() returns null because widget is null."
+                            + " appWidgetId=" + appWidgetId + " userId=" + userId);
+                } else if (widget.provider == null) {
+                    Slog.e(TAG, "getAppWidgetInfo() returns null because widget.provider is null."
+                            + " appWidgetId=" + appWidgetId + " userId=" + userId
+                            + " widget=" + widget);
+                } else {
+                    Slog.e(TAG, "getAppWidgetInfo() returns null because widget.provider is zombie."
+                            + " appWidgetId=" + appWidgetId + " userId=" + userId
+                            + " widget=" + widget);
+                }
             }
-
             return null;
         }
     }
@@ -2022,6 +2057,9 @@
     }
 
     private void deleteHostLocked(Host host) {
+        if (DEBUG) {
+            Slog.i(TAG, "deleteHostLocked() " + host);
+        }
         final int N = host.widgets.size();
         for (int i = N - 1; i >= 0; i--) {
             Widget widget = host.widgets.remove(i);
@@ -2034,6 +2072,9 @@
     }
 
     private void deleteAppWidgetLocked(Widget widget) {
+        if (DEBUG) {
+            Slog.i(TAG, "deleteAppWidgetLocked() " + widget);
+        }
         // We first unbind all services that are bound to this id
         // Check if we need to destroy any services (if no other app widgets are
         // referencing the same service)
@@ -2501,6 +2542,10 @@
                 return widget;
             }
         }
+        if (DEBUG) {
+            Slog.i(TAG, "cannot find widget for appWidgetId=" + appWidgetId + " uid=" + uid
+                    + " packageName=" + packageName);
+        }
         return null;
     }
 
@@ -2618,6 +2663,9 @@
 
     // Remove widgets for provider that are hosted in userId.
     private void deleteWidgetsLocked(Provider provider, int userId) {
+        if (DEBUG) {
+            Slog.i(TAG, "deleteWidgetsLocked() provider=" + provider + " userId=" + userId);
+        }
         final int N = provider.widgets.size();
         for (int i = N - 1; i >= 0; i--) {
             Widget widget = provider.widgets.get(i);
@@ -2955,6 +3003,9 @@
             AppWidgetProviderInfo info = new AppWidgetProviderInfo();
             info.provider = providerId.componentName;
             info.providerInfo = ri.activityInfo;
+            if (DEBUG) {
+                Objects.requireNonNull(ri.activityInfo);
+            }
             return info;
         }
         return null;
@@ -2989,6 +3040,9 @@
             AppWidgetProviderInfo info = new AppWidgetProviderInfo();
             info.provider = providerId.componentName;
             info.providerInfo = activityInfo;
+            if (DEBUG) {
+                Objects.requireNonNull(activityInfo);
+            }
 
             final Resources resources;
             final long identity = Binder.clearCallingIdentity();
@@ -3289,6 +3343,9 @@
      * Adds the widget to mWidgets and tracks the package name in mWidgetPackages.
      */
     void addWidgetLocked(Widget widget) {
+        if (DEBUG) {
+            Slog.i(TAG, "addWidgetLocked() " + widget);
+        }
         mWidgets.add(widget);
 
         onWidgetProviderAddedOrChangedLocked(widget);
@@ -3325,6 +3382,9 @@
      * removes the associated package from the cache.
      */
     void removeWidgetLocked(Widget widget) {
+        if (DEBUG) {
+            Slog.i(TAG, "removeWidgetLocked() " + widget);
+        }
         mWidgets.remove(widget);
         onWidgetRemovedLocked(widget);
         scheduleNotifyAppWidgetRemovedLocked(widget);
@@ -3359,6 +3419,9 @@
      * Clears all widgets and associated cache of packages with bound widgets.
      */
     void clearWidgetsLocked() {
+        if (DEBUG) {
+            Slog.i(TAG, "clearWidgetsLocked()");
+        }
         mWidgets.clear();
 
         onWidgetsClearedLocked();
@@ -3564,6 +3627,9 @@
                             AppWidgetProviderInfo info = new AppWidgetProviderInfo();
                             info.provider = providerId.componentName;
                             info.providerInfo = providerInfo;
+                            if (DEBUG) {
+                                Objects.requireNonNull(providerInfo);
+                            }
 
                             provider = new Provider();
                             provider.setPartialInfoLocked(info);
@@ -3580,6 +3646,9 @@
                             if (info != null) {
                                 info.provider = providerId.componentName;
                                 info.providerInfo = providerInfo;
+                                if (DEBUG) {
+                                    Objects.requireNonNull(providerInfo);
+                                }
                                 provider.setInfoLocked(info);
                             }
                         }
@@ -3714,6 +3783,9 @@
     }
 
     void onUserStopped(int userId) {
+        if (DEBUG) {
+            Slog.i(TAG, "onUserStopped() " + userId);
+        }
         synchronized (mLock) {
             boolean crossProfileWidgetsChanged = false;
 
@@ -3951,6 +4023,10 @@
     }
 
     private boolean removeHostsAndProvidersForPackageLocked(String pkgName, int userId) {
+        if (DEBUG) {
+            Slog.i(TAG, "removeHostsAndProvidersForPackageLocked() pkg=" + pkgName
+                    + " userId=" + userId);
+        }
         boolean removed = removeProvidersForPackageLocked(pkgName, userId);
 
         // Delete the hosts for this package too
@@ -4509,6 +4585,10 @@
                 // have the bind widget permission have access to the widget.
                 return true;
             }
+            if (DEBUG) {
+                Slog.i(TAG, "canAccessAppWidget() failed. packageName=" + packageName
+                        + " uid=" + uid + " userId=" + userId + " widget=" + widget);
+            }
             return false;
         }
 
@@ -4661,6 +4741,9 @@
                     }
                     if (newInfo != null) {
                         info = newInfo;
+                        if (DEBUG) {
+                            Objects.requireNonNull(info);
+                        }
                         updateGeneratedPreviewCategoriesLocked();
                     }
                 }
@@ -4682,12 +4765,18 @@
         @GuardedBy("AppWidgetServiceImpl.mLock")
         public void setPartialInfoLocked(AppWidgetProviderInfo info) {
             this.info = info;
+            if (DEBUG) {
+                Objects.requireNonNull(this.info);
+            }
             mInfoParsed = false;
         }
 
         @GuardedBy("AppWidgetServiceImpl.mLock")
         public void setInfoLocked(AppWidgetProviderInfo info) {
             this.info = info;
+            if (DEBUG) {
+                Objects.requireNonNull(this.info);
+            }
             mInfoParsed = true;
         }
 
@@ -5714,7 +5803,9 @@
                         // so we tear it down in anticipation of it (possibly) being
                         // reconstructed due to the restore
                         host.widgets.remove(widget);
-                        provider.widgets.remove(widget);
+                        if (provider != null) {
+                            provider.widgets.remove(widget);
+                        }
                         // Check if we need to destroy any services (if no other app widgets are
                         // referencing the same service)
                         decrementAppWidgetServiceRefCount(widget);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
index 468b9ab..219b788 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
@@ -36,6 +36,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
 import com.android.internal.inputmethod.IInlineSuggestionsResponseCallback;
+import com.android.internal.inputmethod.InlineSuggestionsRequestCallback;
 import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
 import com.android.server.autofill.ui.InlineFillUi;
 import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -376,8 +377,8 @@
     /**
      * Internal implementation of {@link IInlineSuggestionsRequestCallback}.
      */
-    private static final class InlineSuggestionsRequestCallbackImpl extends
-            IInlineSuggestionsRequestCallback.Stub {
+    private static final class InlineSuggestionsRequestCallbackImpl
+            implements InlineSuggestionsRequestCallback {
 
         private final WeakReference<AutofillInlineSuggestionsRequestSession> mSession;
 
@@ -388,7 +389,7 @@
 
         @BinderThread
         @Override
-        public void onInlineSuggestionsUnsupported() throws RemoteException {
+        public void onInlineSuggestionsUnsupported() {
             if (sDebug) Slog.d(TAG, "onInlineSuggestionsUnsupported() called.");
             final AutofillInlineSuggestionsRequestSession session = mSession.get();
             if (session != null) {
@@ -412,7 +413,7 @@
         }
 
         @Override
-        public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException {
+        public void onInputMethodStartInput(AutofillId imeFieldId) {
             if (sVerbose) Slog.v(TAG, "onInputMethodStartInput() received on " + imeFieldId);
             final AutofillInlineSuggestionsRequestSession session = mSession.get();
             if (session != null) {
@@ -423,7 +424,7 @@
         }
 
         @Override
-        public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException {
+        public void onInputMethodShowInputRequested(boolean requestResult) {
             if (sVerbose) {
                 Slog.v(TAG, "onInputMethodShowInputRequested() received: " + requestResult);
             }
@@ -454,7 +455,7 @@
         }
 
         @Override
-        public void onInputMethodFinishInput() throws RemoteException {
+        public void onInputMethodFinishInput() {
             if (sVerbose) Slog.v(TAG, "onInputMethodFinishInput() received");
             final AutofillInlineSuggestionsRequestSession session = mSession.get();
             if (session != null) {
@@ -466,7 +467,7 @@
 
         @BinderThread
         @Override
-        public void onInlineSuggestionsSessionInvalidated() throws RemoteException {
+        public void onInlineSuggestionsSessionInvalidated() {
             if (sDebug) Slog.d(TAG, "onInlineSuggestionsSessionInvalidated() called.");
             final AutofillInlineSuggestionsRequestSession session = mSession.get();
             if (session != null) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 9701292..6fc05b7 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -1625,13 +1625,13 @@
     final class AutoFillManagerServiceStub extends IAutoFillManager.Stub {
         @Override
         public void addClient(IAutoFillManagerClient client, ComponentName componentName,
-                int userId, IResultReceiver receiver) {
+                int userId, IResultReceiver receiver, boolean credmanRequested) {
             int flags = 0;
             try {
                 synchronized (mLock) {
                     final int enabledFlags =
                             getServiceForUserWithLocalBinderIdentityLocked(userId)
-                            .addClientLocked(client, componentName);
+                            .addClientLocked(client, componentName, credmanRequested);
                     if (enabledFlags != 0) {
                         flags |= enabledFlags;
                     }
@@ -1644,7 +1644,7 @@
                 }
             } catch (Exception ex) {
                 // Don't do anything, send back default flags
-                Log.wtf(TAG, "addClient(): failed " + ex.toString());
+                Log.wtf(TAG, "addClient(): failed " + ex.toString(), ex);
             } finally {
                 send(receiver, flags);
             }
@@ -1998,6 +1998,19 @@
         }
 
         @Override
+        public void setViewAutofilled(int sessionId, @NonNull AutofillId id, int userId) {
+            synchronized (mLock) {
+                final AutofillManagerServiceImpl service =
+                        peekServiceForUserWithLocalBinderIdentityLocked(userId);
+                if (service != null) {
+                    service.setViewAutofilled(sessionId, getCallingUid(), id);
+                } else if (sVerbose) {
+                    Slog.v(TAG, "setAutofillFailure(): no service for " + userId);
+                }
+            }
+        }
+
+        @Override
         public void finishSession(int sessionId, int userId,
                 @AutofillCommitReason int commitReason) {
             synchronized (mLock) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 6822229..588266f 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -33,6 +33,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManagerInternal;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -96,6 +97,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.Random;
 /**
  * Bridge between the {@code system_server}'s {@link AutofillManagerService} and the
@@ -293,19 +295,31 @@
      * @return {@code 0} if disabled, {@code FLAG_ADD_CLIENT_ENABLED} if enabled (it might be
      * OR'ed with {@code FLAG_AUGMENTED_AUTOFILL_REQUEST}).
      */
-    @GuardedBy("mLock")
-    int addClientLocked(IAutoFillManagerClient client, ComponentName componentName) {
-        if (mClients == null) {
-            mClients = new RemoteCallbackList<>();
-        }
-        mClients.register(client);
+    int addClientLocked(IAutoFillManagerClient client, ComponentName componentName,
+            boolean credmanRequested) {
+        synchronized (mLock) {
+            ComponentName credComponentName = getCredentialAutofillService(getContext());
 
-        if (isEnabledLocked()) return FLAG_ADD_CLIENT_ENABLED;
+            if (!credmanRequested
+                    && Objects.equals(credComponentName,
+                    mInfo == null ? null : mInfo.getServiceInfo().getComponentName())) {
+                // If the service component name corresponds to cred component name, then it means
+                // no autofill provider is selected by the user. Cred Autofill Service should only
+                // be active if there is a credman request.
+                return 0;
+            }
+            if (mClients == null) {
+                mClients = new RemoteCallbackList<>();
+            }
+            mClients.register(client);
 
-        // Check if it's enabled for augmented autofill
-        if (componentName != null && isAugmentedAutofillServiceAvailableLocked()
-                && isWhitelistedForAugmentedAutofillLocked(componentName)) {
-            return FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY;
+            if (isEnabledLocked()) return FLAG_ADD_CLIENT_ENABLED;
+
+            // Check if it's enabled for augmented autofill
+            if (componentName != null && isAugmentedAutofillServiceAvailableLocked()
+                    && isWhitelistedForAugmentedAutofillLocked(componentName)) {
+                return FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY;
+            }
         }
 
         // No flags / disabled
@@ -452,6 +466,7 @@
     @GuardedBy("mLock")
     void setAutofillFailureLocked(int sessionId, int uid, @NonNull List<AutofillId> ids) {
         if (!isEnabledLocked()) {
+            Slog.wtf(TAG, "Service not enabled");
             return;
         }
         final Session session = mSessions.get(sessionId);
@@ -463,8 +478,23 @@
     }
 
     @GuardedBy("mLock")
+    void setViewAutofilled(int sessionId, int uid, @NonNull AutofillId id) {
+        if (!isEnabledLocked()) {
+            Slog.wtf(TAG, "Service not enabled");
+            return;
+        }
+        final Session session = mSessions.get(sessionId);
+        if (session == null || uid != session.uid) {
+            Slog.v(TAG, "setViewAutofilled(): no session for " + sessionId + "(" + uid + ")");
+            return;
+        }
+        session.setViewAutofilled(id);
+    }
+
+    @GuardedBy("mLock")
     void finishSessionLocked(int sessionId, int uid, @AutofillCommitReason int commitReason) {
         if (!isEnabledLocked()) {
+            Slog.wtf(TAG, "Service not enabled");
             return;
         }
 
@@ -1486,6 +1516,22 @@
         return true;
     }
 
+    @Nullable
+    private ComponentName getCredentialAutofillService(Context context) {
+        ComponentName componentName = null;
+        String credentialManagerAutofillCompName = context.getResources().getString(
+                R.string.config_defaultCredentialManagerAutofillService);
+        if (credentialManagerAutofillCompName != null
+                && !credentialManagerAutofillCompName.isEmpty()) {
+            componentName = ComponentName.unflattenFromString(
+                    credentialManagerAutofillCompName);
+        }
+        if (componentName == null) {
+            Slog.w(TAG, "Invalid CredentialAutofillService");
+        }
+        return componentName;
+    }
+
     @GuardedBy("mLock")
     private int getAugmentedAutofillServiceUidLocked() {
         if (mRemoteAugmentedAutofillServiceInfo == null) {
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 9c84b12..f289115 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -65,6 +65,7 @@
 import android.provider.Settings;
 import android.service.autofill.Dataset;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
@@ -548,9 +549,10 @@
     /**
      * Set views_fillable_total_count as long as mEventInternal presents.
      */
-    public void maybeSetViewFillableCounts(int totalFillableCount) {
+    public void maybeSetViewFillablesAndCount(List<AutofillId> autofillIds) {
         mEventInternal.ifPresent(event -> {
-            event.mViewFillableTotalCount = totalFillableCount;
+            event.mAutofillIdsAttemptedAutofill = new ArraySet<>(autofillIds);
+            event.mViewFillableTotalCount = event.mAutofillIdsAttemptedAutofill.size();
         });
     }
 
@@ -564,6 +566,41 @@
         });
     }
 
+    /** Sets focused_autofill_id using view id */
+    public void maybeSetFocusedId(AutofillId id) {
+        maybeSetFocusedId(id.getViewId());
+    }
+
+    /** Sets focused_autofill_id as long as mEventInternal is present */
+    public void maybeSetFocusedId(int id) {
+        mEventInternal.ifPresent(event -> {
+            event.mFocusedId = id;
+        });
+    }
+    /**
+     * Set views_filled_failure_count using failure count as long as mEventInternal
+     * presents.
+     */
+    public void maybeAddSuccessId(AutofillId autofillId) {
+        mEventInternal.ifPresent(event -> {
+            ArraySet<AutofillId> autofillIds = event.mAutofillIdsAttemptedAutofill;
+            if (autofillIds == null) {
+                Slog.w(TAG, "Attempted autofill ids is null, but received autofillId:" + autofillId
+                        + " successfully filled");
+                event.mViewFilledButUnexpectedCount++;
+            } else if (autofillIds.contains(autofillId)) {
+                if (sVerbose) {
+                    Slog.v(TAG, "Logging autofill for id:" + autofillId);
+                    event.mViewFillSuccessCount++;
+                }
+            } else {
+                Slog.w(TAG, "Successfully filled autofillId:" + autofillId
+                        + " not found in list of attempted autofill ids: " + autofillIds);
+                event.mViewFilledButUnexpectedCount++;
+            }
+        });
+    }
+
     public void logAndEndEvent() {
         if (!mEventInternal.isPresent()) {
             Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same "
@@ -608,7 +645,10 @@
                     + " mIsCredentialRequest=" + event.mIsCredentialRequest
                     + " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential
                     + " mViewFillableTotalCount=" + event.mViewFillableTotalCount
-                    + " mViewFillFailureCount=" + event.mViewFillFailureCount);
+                    + " mViewFillFailureCount=" + event.mViewFillFailureCount
+                    + " mFocusedId=" + event.mFocusedId
+                    + " mViewFillSuccessCount=" + event.mViewFillSuccessCount
+                    + " mViewFilledButUnexpectedCount=" + event.mViewFilledButUnexpectedCount);
         }
 
         // TODO(b/234185326): Distinguish empty responses from other no presentation reasons.
@@ -651,7 +691,10 @@
                 event.mIsCredentialRequest,
                 event.mWebviewRequestedCredential,
                 event.mViewFillableTotalCount,
-                event.mViewFillFailureCount);
+                event.mViewFillFailureCount,
+                event.mFocusedId,
+                event.mViewFillSuccessCount,
+                event.mViewFilledButUnexpectedCount);
         mEventInternal = Optional.empty();
     }
 
@@ -689,7 +732,14 @@
         boolean mWebviewRequestedCredential = false;
         int mViewFillableTotalCount = -1;
         int mViewFillFailureCount = -1;
+        int mFocusedId = -1;
 
+        // Default value for success count is set to 0 explicitly. Setting it to -1 for
+        // uninitialized doesn't help much, as this would be non-zero only if callback is received.
+        int mViewFillSuccessCount = 0;
+        int mViewFilledButUnexpectedCount = 0;
+
+        ArraySet<AutofillId> mAutofillIdsAttemptedAutofill;
         PresentationStatsEventInternal() {}
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/SaveEventLogger.java b/services/autofill/java/com/android/server/autofill/SaveEventLogger.java
index 4f95893..b7f12ad 100644
--- a/services/autofill/java/com/android/server/autofill/SaveEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/SaveEventLogger.java
@@ -117,7 +117,7 @@
 
   private final int mSessionId;
   private Optional<SaveEventInternal> mEventInternal;
-  private long mSessionStartTimestamp;
+  private final long mSessionStartTimestamp;
 
   private SaveEventLogger(int sessionId, long sessionStartTimestamp) {
       mSessionId = sessionId;
@@ -227,13 +227,11 @@
     });
   }
 
-  /* Returns timestamp (relative to mSessionStartTimestamp) or  UNINITIATED_TIMESTAMP if mSessionStartTimestamp is not set */
-  private long getCurrentTimestamp() {
-    long timestamp = UNINITIATED_TIMESTAMP;
-    if (mSessionStartTimestamp != UNINITIATED_TIMESTAMP) {
-      timestamp = SystemClock.elapsedRealtime() - mSessionStartTimestamp;
-    }
-    return timestamp;
+  /**
+   * Returns timestamp (relative to mSessionStartTimestamp)
+   */
+  private long getElapsedTime() {
+    return SystemClock.elapsedRealtime() - mSessionStartTimestamp;
   }
 
   /**
@@ -247,7 +245,7 @@
 
   /** Set latency_save_ui_display_millis as long as mEventInternal presents. */
   public void maybeSetLatencySaveUiDisplayMillis() {
-    maybeSetLatencySaveUiDisplayMillis(getCurrentTimestamp());
+    maybeSetLatencySaveUiDisplayMillis(getElapsedTime());
   }
 
   /**
@@ -261,7 +259,7 @@
 
   /** Set latency_save_request_millis as long as mEventInternal presents. */
   public void maybeSetLatencySaveRequestMillis() {
-    maybeSetLatencySaveRequestMillis(getCurrentTimestamp());
+    maybeSetLatencySaveRequestMillis(getElapsedTime());
   }
 
   /**
@@ -275,7 +273,7 @@
 
   /** Set latency_save_finish_millis as long as mEventInternal presents. */
   public void maybeSetLatencySaveFinishMillis() {
-    maybeSetLatencySaveFinishMillis(getCurrentTimestamp());
+    maybeSetLatencySaveFinishMillis(getElapsedTime());
   }
 
   /**
@@ -288,6 +286,16 @@
   }
 
   /**
+   * Set autofill_service_uid as long as mEventInternal presents.
+   */
+  public void maybeSetAutofillServiceUid(int uid) {
+        mEventInternal.ifPresent(
+                event -> {
+                    event.mServiceUid = uid;
+                });
+  }
+
+  /**
    * Log an AUTOFILL_SAVE_EVENT_REPORTED event.
    */
   public void logAndEndEvent() {
@@ -314,7 +322,8 @@
           + " mLatencySaveUiDisplayMillis=" + event.mLatencySaveUiDisplayMillis
           + " mLatencySaveRequestMillis=" + event.mLatencySaveRequestMillis
           + " mLatencySaveFinishMillis=" + event.mLatencySaveFinishMillis
-          + " mIsFrameworkCreatedSaveInfo=" + event.mIsFrameworkCreatedSaveInfo);
+          + " mIsFrameworkCreatedSaveInfo=" + event.mIsFrameworkCreatedSaveInfo
+          + " mServiceUid=" + event.mServiceUid);
     }
     FrameworkStatsLog.write(
         AUTOFILL_SAVE_EVENT_REPORTED,
@@ -333,7 +342,8 @@
         event.mLatencySaveUiDisplayMillis,
         event.mLatencySaveRequestMillis,
         event.mLatencySaveFinishMillis,
-        event.mIsFrameworkCreatedSaveInfo);
+        event.mIsFrameworkCreatedSaveInfo,
+        event.mServiceUid);
     mEventInternal = Optional.empty();
   }
 
@@ -349,11 +359,11 @@
     boolean mCancelButtonClicked = false;
     boolean mDialogDismissed = false;
     boolean mIsSaved = false;
-    long mLatencySaveUiDisplayMillis = 0;
-    long mLatencySaveRequestMillis = 0;
-    long mLatencySaveFinishMillis = 0;
+    long mLatencySaveUiDisplayMillis = UNINITIATED_TIMESTAMP;
+    long mLatencySaveRequestMillis = UNINITIATED_TIMESTAMP;
+    long mLatencySaveFinishMillis = UNINITIATED_TIMESTAMP;
     boolean mIsFrameworkCreatedSaveInfo = false;
-
+    int mServiceUid = -1;
     SaveEventInternal() {
     }
   }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index ba6b067..a8b1235 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1168,6 +1168,7 @@
         return null;
     }
 
+    @GuardedBy("mLock")
     @Nullable
     private AutofillValue findValueFromThisSessionOnlyLocked(@NonNull AutofillId autofillId) {
         final ViewState state = mViewStates.get(autofillId);
@@ -1176,9 +1177,25 @@
             return null;
         }
         AutofillValue value = state.getCurrentValue();
+
+        // Some app clears the form before navigating to another activities. In this case, use the
+        // cached value instead.
+        if (value == null || value.isEmpty()) {
+            AutofillValue candidateSaveValue = state.getCandidateSaveValue();
+            if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) {
+                if (sDebug) {
+                    Slog.d(TAG, "findValueLocked(): current value for " + autofillId
+                            + " is empty, using candidateSaveValue instead.");
+                }
+                return candidateSaveValue;
+            }
+        }
         if (value == null) {
-            if (sDebug) Slog.d(TAG, "findValueLocked(): no current value for " + autofillId);
-            value = getValueFromContextsLocked(autofillId);
+            if (sDebug) {
+                Slog.d(TAG, "findValueLocked(): no current value for " + autofillId
+                        + ", checking value from previous fill contexts");
+                value = getValueFromContextsLocked(autofillId);
+            }
         }
         return value;
     }
@@ -1336,8 +1353,11 @@
         mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested);
         mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(
                 mFieldClassificationIdSnapshot);
+        mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
         mFillRequestEventLogger.maybeSetRequestId(requestId);
         mFillRequestEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
+        mSaveEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
+        mSessionCommittedEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
         if (mSessionFlags.mInlineSupportedByService) {
             mFillRequestEventLogger.maybeSetInlineSuggestionHostUid(mContext, userId);
         }
@@ -1493,10 +1513,16 @@
 
         mCredentialAutofillService = getCredentialAutofillService(context);
 
-        ComponentName primaryServiceComponentName, secondaryServiceComponentName;
+        ComponentName primaryServiceComponentName, secondaryServiceComponentName = null;
         if (isPrimaryCredential) {
             primaryServiceComponentName = mCredentialAutofillService;
-            secondaryServiceComponentName = serviceComponentName;
+            if (serviceComponentName != null
+                    && !serviceComponentName.equals(mCredentialAutofillService)) {
+                // if service component name is credential autofill service, no need to initialize
+                // secondary provider. This happens if the user sets non-autofill provider as
+                // password provider.
+                secondaryServiceComponentName = serviceComponentName;
+            }
         } else {
             primaryServiceComponentName = serviceComponentName;
             secondaryServiceComponentName = mCredentialAutofillService;
@@ -2440,9 +2466,7 @@
             mSessionFlags.mShowingSaveUi = false;
             // Log onSaveRequest result.
             mSaveEventLogger.maybeSetIsSaved(true);
-            final long saveRequestFinishTimestamp =
-                SystemClock.elapsedRealtime() - mLatencyBaseTime;
-            mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp);
+            mSaveEventLogger.maybeSetLatencySaveFinishMillis();
             mSaveEventLogger.logAndEndEvent();
             if (mDestroyed) {
                 Slog.w(TAG, "Call to Session#onSaveRequestSuccess() rejected - session: "
@@ -2473,9 +2497,7 @@
         synchronized (mLock) {
             mSessionFlags.mShowingSaveUi = false;
             // Log onSaveRequest result.
-            final long saveRequestFinishTimestamp =
-                SystemClock.elapsedRealtime() - mLatencyBaseTime;
-            mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp);
+            mSaveEventLogger.maybeSetLatencySaveFinishMillis();
             mSaveEventLogger.logAndEndEvent();
             if (mDestroyed) {
                 Slog.w(TAG, "Call to Session#onSaveRequestFailure() rejected - session: "
@@ -2621,8 +2643,7 @@
                 return;
             }
         }
-        final long saveRequestStartTimestamp = SystemClock.elapsedRealtime() - mLatencyBaseTime;
-        mSaveEventLogger.maybeSetLatencySaveRequestMillis(saveRequestStartTimestamp);
+        mSaveEventLogger.maybeSetLatencySaveRequestMillis();
         mHandler.sendMessage(obtainMessage(
                 AutofillManagerServiceImpl::handleSessionSave,
                 mService, this));
@@ -3713,19 +3734,34 @@
 
                 AutofillValue value = viewState.getCurrentValue();
                 if (value == null || value.isEmpty()) {
-                    final AutofillValue initialValue = getValueFromContextsLocked(id);
-                    if (initialValue != null) {
-                        if (sDebug) {
-                            Slog.d(TAG, "Value of required field " + id + " didn't change; "
-                                    + "using initial value (" + initialValue + ") instead");
+                    // Some apps clear the form before navigating to other activities.
+                    // If current value is empty, consider fall back to last cached
+                    // non-empty result first.
+                    final AutofillValue candidateSaveValue =
+                            viewState.getCandidateSaveValue();
+                    if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) {
+                        if (sVerbose) {
+                            Slog.v(TAG, "current value is empty, using cached last non-empty "
+                                    + "value instead");
                         }
-                        value = initialValue;
+                        value = candidateSaveValue;
                     } else {
-                        if (sDebug) {
-                            Slog.d(TAG, "empty value for required " + id );
+                        // If candidate save value is also empty, consider falling back to initial
+                        // value in context.
+                        final AutofillValue initialValue = getValueFromContextsLocked(id);
+                        if (initialValue != null) {
+                            if (sDebug) {
+                                Slog.d(TAG, "Value of required field " + id + " didn't change; "
+                                        + "using initial value (" + initialValue + ") instead");
+                            }
+                            value = initialValue;
+                        } else {
+                            if (sDebug) {
+                                Slog.d(TAG, "empty value for required " + id);
+                            }
+                            allRequiredAreNotEmpty = false;
+                            break;
                         }
-                        allRequiredAreNotEmpty = false;
-                        break;
                     }
                 }
 
@@ -3797,7 +3833,21 @@
                         continue;
                     }
                     if ((viewState.getState() & ViewState.STATE_CHANGED) != 0) {
-                        final AutofillValue currentValue = viewState.getCurrentValue();
+                        AutofillValue currentValue = viewState.getCurrentValue();
+                        if (currentValue == null || currentValue.isEmpty()) {
+                            // Some apps clear the form before navigating to other activities.
+                            // If current value is empty, consider fall back to last cached
+                            // non-empty result instead.
+                            final AutofillValue candidateSaveValue =
+                                    viewState.getCandidateSaveValue();
+                            if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) {
+                                if (sVerbose) {
+                                    Slog.v(TAG, "current value is empty, using cached last "
+                                            + "non-empty value instead");
+                                }
+                                currentValue = candidateSaveValue;
+                            }
+                        }
                         final AutofillValue value = getSanitizedValue(sanitizers, id, currentValue);
                         if (value == null) {
                             if (sDebug) {
@@ -4665,6 +4715,7 @@
                 mFieldClassificationIdSnapshot);
         mPresentationStatsEventLogger.maybeSetAvailableCount(
                 response.getDatasets(), mCurrentViewId);
+        mPresentationStatsEventLogger.maybeSetFocusedId(mCurrentViewId);
     }
 
     @GuardedBy("mLock")
@@ -4709,14 +4760,18 @@
     @GuardedBy("mLock")
     private void updateViewStateAndUiOnValueChangedLocked(AutofillId id, AutofillValue value,
             ViewState viewState, int flags) {
+        // Cache the last non-empty value for save purpose. Some apps clear the form before
+        // navigating to other activities.
         if (mIgnoreViewStateResetToEmpty && (value == null || value.isEmpty())
                 && viewState.getCurrentValue() != null && viewState.getCurrentValue().isText()
                 && viewState.getCurrentValue().getTextValue() != null
                 && viewState.getCurrentValue().getTextValue().length() > 1) {
             if (sVerbose) {
-                Slog.v(TAG, "Ignoring view state reset to empty on id " + id);
+                Slog.v(TAG, "value is resetting to empty, caching the last non-empty value");
             }
-            return;
+            viewState.setCandidateSaveValue(viewState.getCurrentValue());
+        } else {
+            viewState.setCandidateSaveValue(null);
         }
         final String textValue;
         if (value == null || !value.isText()) {
@@ -5377,7 +5432,20 @@
             }
         }
         mPresentationStatsEventLogger.maybeSetViewFillFailureCounts(ids.size());
-        mPresentationStatsEventLogger.logAndEndEvent();
+    }
+
+    /**
+     * Sets the state of views that failed to autofill.
+     */
+    @GuardedBy("mLock")
+    void setViewAutofilled(@NonNull AutofillId id) {
+        if (sVerbose) {
+            Slog.v(TAG, "View autofilled: " + id);
+        }
+        if (id.getSessionId() == AutofillId.NO_SESSION) {
+            id.setSessionId(this.id);
+        }
+        mPresentationStatsEventLogger.maybeAddSuccessId(id);
     }
 
     @GuardedBy("mLock")
@@ -6585,7 +6653,7 @@
                     if (sVerbose) {
                         Slog.v(TAG, "Total views to be autofilled: " + ids.size());
                     }
-                    mPresentationStatsEventLogger.maybeSetViewFillableCounts(ids.size());
+                    mPresentationStatsEventLogger.maybeSetViewFillablesAndCount(ids);
                     if (sDebug) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
                     mClient.autofill(id, ids, values, hideHighlight);
                     if (dataset.getId() != null) {
diff --git a/services/autofill/java/com/android/server/autofill/SessionCommittedEventLogger.java b/services/autofill/java/com/android/server/autofill/SessionCommittedEventLogger.java
index cd37073..1be8548 100644
--- a/services/autofill/java/com/android/server/autofill/SessionCommittedEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/SessionCommittedEventLogger.java
@@ -17,21 +17,15 @@
 package com.android.server.autofill;
 
 import static android.view.autofill.AutofillManager.COMMIT_REASON_UNKNOWN;
+
 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SESSION_COMMITTED;
 import static com.android.server.autofill.Helper.sVerbose;
 
-import android.annotation.IntDef;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.provider.Settings;
-import android.text.TextUtils;
 import android.util.Slog;
 import android.view.autofill.AutofillManager.AutofillCommitReason;
+
 import com.android.internal.util.FrameworkStatsLog;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.Optional;
 
 /**
@@ -91,6 +85,14 @@
     });
   }
 
+  /** Set autofill_service_uid as long as mEventInternal presents. */
+  public void maybeSetAutofillServiceUid(int uid) {
+        mEventInternal.ifPresent(
+                event -> {
+                    event.mServiceUid = uid;
+                });
+  }
+
   /**
    * Log an AUTOFILL_SESSION_COMMITTED event.
    */
@@ -106,7 +108,8 @@
           + " mComponentPackageUid=" + event.mComponentPackageUid
           + " mRequestCount=" + event.mRequestCount
           + " mCommitReason=" + event.mCommitReason
-          + " mSessionDurationMillis=" + event.mSessionDurationMillis);
+          + " mSessionDurationMillis=" + event.mSessionDurationMillis
+          + " mServiceUid=" + event.mServiceUid);
     }
     FrameworkStatsLog.write(
         AUTOFILL_SESSION_COMMITTED,
@@ -114,7 +117,8 @@
         event.mComponentPackageUid,
         event.mRequestCount,
         event.mCommitReason,
-        event.mSessionDurationMillis);
+        event.mSessionDurationMillis,
+        event.mServiceUid);
     mEventInternal = Optional.empty();
   }
 
@@ -123,6 +127,7 @@
     int mRequestCount = 0;
     int mCommitReason = COMMIT_REASON_UNKNOWN;
     long mSessionDurationMillis = 0;
+    int mServiceUid = -1;
 
     SessionCommittedEventInternal() {
     }
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
index fec5aa5..6ad0eb6 100644
--- a/services/autofill/java/com/android/server/autofill/ViewState.java
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -106,6 +106,15 @@
      */
     private FillResponse mSecondaryFillResponse;
     private AutofillValue mCurrentValue;
+
+    /**
+     * Some apps clear the form before navigating to another activity. The mCandidateSaveValue
+     * caches the value when a field with string longer than 2 characters are cleared.
+     *
+     * When showing save UI, if mCurrentValue of view state is empty, session would use
+     * mCandidateSaveValue to prompt save instead.
+     */
+    private AutofillValue mCandidateSaveValue;
     private AutofillValue mAutofilledValue;
     private AutofillValue mSanitizedValue;
     private Rect mVirtualBounds;
@@ -139,6 +148,18 @@
         mCurrentValue = value;
     }
 
+    /**
+     * Gets the candidate save value of the view.
+     */
+    @Nullable
+    AutofillValue getCandidateSaveValue() {
+        return mCandidateSaveValue;
+    }
+
+    void setCandidateSaveValue(AutofillValue value) {
+        mCandidateSaveValue = value;
+    }
+
     @Nullable
     AutofillValue getAutofilledValue() {
         return mAutofilledValue;
@@ -268,6 +289,9 @@
         if (mCurrentValue != null) {
             builder.append(", currentValue:" ).append(mCurrentValue);
         }
+        if (mCandidateSaveValue != null) {
+            builder.append(", candidateSaveValue:").append(mCandidateSaveValue);
+        }
         if (mAutofilledValue != null) {
             builder.append(", autofilledValue:" ).append(mAutofilledValue);
         }
@@ -302,6 +326,9 @@
         if (mAutofilledValue != null) {
             pw.print(prefix); pw.print("autofilledValue:" ); pw.println(mAutofilledValue);
         }
+        if (mCandidateSaveValue != null) {
+            pw.print(prefix); pw.print("candidateSaveValue:"); pw.println(mCandidateSaveValue);
+        }
         if (mSanitizedValue != null) {
             pw.print(prefix); pw.print("sanitizedValue:" ); pw.println(mSanitizedValue);
         }
diff --git a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
index 70443f9..38a412f 100644
--- a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
+++ b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
@@ -118,7 +118,7 @@
         final InputMethodManagerInternal inputMethodManagerInternal =
                 LocalServices.getService(InputMethodManagerInternal.class);
         if (!inputMethodManagerInternal.transferTouchFocusToImeWindow(sourceInputToken,
-                displayId)) {
+                displayId, mUserId)) {
             Slog.e(TAG, "Cannot transfer touch focus from suggestion to IME");
             mOnErrorCallback.run();
         }
diff --git a/services/backup/java/com/android/server/backup/transport/TransportConnection.java b/services/backup/java/com/android/server/backup/transport/TransportConnection.java
index 1009787..67ebb3e 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportConnection.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportConnection.java
@@ -658,11 +658,13 @@
      * This class is a proxy to TransportClient methods that doesn't hold a strong reference to the
      * TransportClient, allowing it to be GC'ed. If the reference was lost it logs a message.
      */
-    private static class TransportConnectionMonitor implements ServiceConnection {
+    @VisibleForTesting
+    static class TransportConnectionMonitor implements ServiceConnection {
         private final Context mContext;
         private final WeakReference<TransportConnection> mTransportClientRef;
 
-        private TransportConnectionMonitor(Context context,
+        @VisibleForTesting
+        TransportConnectionMonitor(Context context,
                 TransportConnection transportConnection) {
             mContext = context;
             mTransportClientRef = new WeakReference<>(transportConnection);
@@ -704,7 +706,13 @@
 
         /** @see TransportConnection#finalize() */
         private void referenceLost(String caller) {
-            mContext.unbindService(this);
+            try {
+                mContext.unbindService(this);
+            } catch (IllegalArgumentException e) {
+                TransportUtils.log(Priority.WARN, TAG,
+                        caller + " called but unbindService failed: " + e.getMessage());
+                return;
+            }
             TransportUtils.log(
                     Priority.INFO,
                     TAG,
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 9069689..026d29c 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -135,7 +135,7 @@
      */
     public PendingIntent buildPermissionTransferUserConsentIntent(String packageName,
             @UserIdInt int userId, int associationId) {
-        if (PackageUtils.isPackageAllowlisted(mContext, mPackageManager, packageName)) {
+        if (PackageUtils.isPermSyncAutoEnabled(mContext, mPackageManager, packageName)) {
             Slog.i(LOG_TAG, "User consent Intent should be skipped. Returning null.");
             // Auto enable perm sync for the allowlisted packages, but don't override user decision
             PermissionSyncRequest request = getPermissionSyncRequest(associationId);
diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
index 0e66fbc..71a1822 100644
--- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
+++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
@@ -23,6 +23,7 @@
 import android.os.Build;
 import android.util.Slog;
 
+import com.google.security.cryptauth.lib.securegcm.ukey2.AlertException;
 import com.google.security.cryptauth.lib.securegcm.ukey2.BadHandleException;
 import com.google.security.cryptauth.lib.securegcm.ukey2.CryptoException;
 import com.google.security.cryptauth.lib.securegcm.ukey2.D2DConnectionContextV1;
@@ -203,7 +204,8 @@
      *
      * This method must only be called from one of the two participants.
      */
-    public void establishSecureConnection() throws IOException, SecureChannelException {
+    public void establishSecureConnection() throws IOException,
+            SecureChannelException, HandshakeException {
         if (isSecured()) {
             Slog.d(TAG, "Channel is already secure.");
             return;
@@ -334,7 +336,7 @@
         }
     }
 
-    private void initiateHandshake() throws IOException, BadHandleException {
+    private void initiateHandshake() throws IOException, BadHandleException , HandshakeException {
         if (mConnectionContext != null) {
             Slog.d(TAG, "Ukey2 handshake is already completed.");
             return;
@@ -394,8 +396,8 @@
         }
     }
 
-    private void exchangeHandshake()
-            throws IOException, HandshakeException, BadHandleException, CryptoException {
+    private void exchangeHandshake() throws IOException, HandshakeException,
+            BadHandleException, CryptoException, AlertException {
         if (mConnectionContext != null) {
             Slog.d(TAG, "Ukey2 handshake is already completed.");
             return;
diff --git a/services/companion/java/com/android/server/companion/utils/PackageUtils.java b/services/companion/java/com/android/server/companion/utils/PackageUtils.java
index 254d28b..94ab9dd 100644
--- a/services/companion/java/com/android/server/companion/utils/PackageUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/PackageUtils.java
@@ -21,6 +21,11 @@
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
 import static android.os.Binder.getCallingUid;
 
+import static com.android.internal.R.array.config_companionDeviceCerts;
+import static com.android.internal.R.array.config_companionDevicePackages;
+import static com.android.internal.R.array.config_companionPermSyncEnabledCerts;
+import static com.android.internal.R.array.config_companionPermSyncEnabledPackages;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -185,15 +190,30 @@
      */
     public static boolean isPackageAllowlisted(Context context,
             PackageManagerInternal packageManagerInternal, @NonNull String packageName) {
-        final String[] allowlistedPackages = context.getResources()
-                .getStringArray(com.android.internal.R.array.config_companionDevicePackages);
+        return isPackageAllowlisted(context, packageManagerInternal, packageName,
+                config_companionDevicePackages, config_companionDeviceCerts);
+    }
+
+    /**
+     * Check if perm sync is allowlisted and auto-enabled for the package.
+     */
+    public static boolean isPermSyncAutoEnabled(Context context,
+            PackageManagerInternal packageManagerInternal, String packageName) {
+        return isPackageAllowlisted(context, packageManagerInternal, packageName,
+                config_companionPermSyncEnabledPackages, config_companionPermSyncEnabledCerts);
+    }
+
+    private static boolean isPackageAllowlisted(Context context,
+            PackageManagerInternal packageManagerInternal, String packageName,
+            int packagesConfig, int certsConfig) {
+        final String[] allowlistedPackages = context.getResources().getStringArray(packagesConfig);
         if (!ArrayUtils.contains(allowlistedPackages, packageName)) {
             Slog.d(TAG, packageName + " is not allowlisted.");
             return false;
         }
 
         final String[] allowlistedPackagesSignatureDigests = context.getResources()
-                .getStringArray(com.android.internal.R.array.config_companionDeviceCerts);
+                .getStringArray(certsConfig);
         final Set<String> allowlistedSignatureDigestsForRequestingPackage = new HashSet<>();
         for (int i = 0; i < allowlistedPackages.length; i++) {
             if (allowlistedPackages[i].equals(packageName)) {
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 9b72288..70af49c 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -41,7 +41,6 @@
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
-import android.view.Display;
 import android.view.InputDevice;
 import android.view.WindowManager;
 
@@ -169,7 +168,6 @@
         createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId,
                 deviceToken, displayId, phys,
                 () -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys));
-        setVirtualMousePointerDisplayId(displayId);
     }
 
     void createTouchscreen(@NonNull String deviceName, int vendorId, int productId,
@@ -226,7 +224,7 @@
         token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0);
         mNativeWrapper.closeUinput(inputDeviceDescriptor.getNativePointer());
         String phys = inputDeviceDescriptor.getPhys();
-        InputManagerGlobal.getInstance().removeUniqueIdAssociation(phys);
+        InputManagerGlobal.getInstance().removeUniqueIdAssociationByPort(phys);
         // Type associations are added in the case of navigation touchpads. Those should be removed
         // once the input device gets closed.
         if (inputDeviceDescriptor.getType() == InputDeviceDescriptor.TYPE_NAVIGATION_TOUCHPAD) {
@@ -236,15 +234,6 @@
         if (inputDeviceDescriptor.getType() == InputDeviceDescriptor.TYPE_KEYBOARD) {
             mInputManagerInternal.removeKeyboardLayoutAssociation(phys);
         }
-
-        // Reset values to the default if all virtual mice are unregistered, or set display
-        // id if there's another mouse (choose the most recent). The inputDeviceDescriptor must be
-        // removed from the mInputDeviceDescriptors instance variable prior to this point.
-        if (inputDeviceDescriptor.isMouse()) {
-            if (getVirtualMousePointerDisplayId() == inputDeviceDescriptor.getDisplayId()) {
-                updateActivePointerDisplayIdLocked();
-            }
-        }
     }
 
     /**
@@ -276,29 +265,6 @@
         mWindowManager.setDisplayImePolicy(displayId, policy);
     }
 
-    // TODO(b/293587049): Remove after pointer icon refactor is complete.
-    @GuardedBy("mLock")
-    private void updateActivePointerDisplayIdLocked() {
-        InputDeviceDescriptor mostRecentlyCreatedMouse = null;
-        for (int i = 0; i < mInputDeviceDescriptors.size(); ++i) {
-            InputDeviceDescriptor otherInputDeviceDescriptor = mInputDeviceDescriptors.valueAt(i);
-            if (otherInputDeviceDescriptor.isMouse()) {
-                if (mostRecentlyCreatedMouse == null
-                        || (otherInputDeviceDescriptor.getCreationOrderNumber()
-                        > mostRecentlyCreatedMouse.getCreationOrderNumber())) {
-                    mostRecentlyCreatedMouse = otherInputDeviceDescriptor;
-                }
-            }
-        }
-        if (mostRecentlyCreatedMouse != null) {
-            setVirtualMousePointerDisplayId(
-                    mostRecentlyCreatedMouse.getDisplayId());
-        } else {
-            // All mice have been unregistered
-            setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
-        }
-    }
-
     /**
      * Validates a device name by checking whether a device with the same name already exists.
      * @param deviceName The name of the device to be validated
@@ -321,7 +287,7 @@
 
     private void setUniqueIdAssociation(int displayId, String phys) {
         final String displayUniqueId = mDisplayManagerInternal.getDisplayInfo(displayId).uniqueId;
-        InputManagerGlobal.getInstance().addUniqueIdAssociation(phys, displayUniqueId);
+        InputManagerGlobal.getInstance().addUniqueIdAssociationByPort(phys, displayUniqueId);
     }
 
     boolean sendDpadKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) {
@@ -355,9 +321,6 @@
             if (inputDeviceDescriptor == null) {
                 return false;
             }
-            if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) {
-                setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId());
-            }
             return mNativeWrapper.writeButtonEvent(inputDeviceDescriptor.getNativePointer(),
                     event.getButtonCode(), event.getAction(), event.getEventTimeNanos());
         }
@@ -384,9 +347,6 @@
             if (inputDeviceDescriptor == null) {
                 return false;
             }
-            if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) {
-                setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId());
-            }
             return mNativeWrapper.writeRelativeEvent(inputDeviceDescriptor.getNativePointer(),
                     event.getRelativeX(), event.getRelativeY(), event.getEventTimeNanos());
         }
@@ -399,9 +359,6 @@
             if (inputDeviceDescriptor == null) {
                 return false;
             }
-            if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) {
-                setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId());
-            }
             return mNativeWrapper.writeScrollEvent(inputDeviceDescriptor.getNativePointer(),
                     event.getXAxisMovement(), event.getYAxisMovement(), event.getEventTimeNanos());
         }
@@ -415,9 +372,6 @@
                 throw new IllegalArgumentException(
                         "Could not get cursor position for input device for given token");
             }
-            if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) {
-                setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId());
-            }
             return LocalServices.getService(InputManagerInternal.class).getCursorPosition(
                     inputDeviceDescriptor.getDisplayId());
         }
@@ -835,7 +789,7 @@
                 throw e;
             }
         } catch (DeviceCreationException e) {
-            InputManagerGlobal.getInstance().removeUniqueIdAssociation(phys);
+            InputManagerGlobal.getInstance().removeUniqueIdAssociationByPort(phys);
             throw e;
         }
 
@@ -878,22 +832,4 @@
         /** Returns true if the calling thread is a valid thread for device creation. */
         boolean isValidThread();
     }
-
-    // TODO(b/293587049): Remove after pointer icon refactor is complete.
-    private void setVirtualMousePointerDisplayId(int displayId) {
-        if (com.android.input.flags.Flags.enablePointerChoreographer()) {
-            // We no longer need to set the pointer display when pointer choreographer is enabled.
-            return;
-        }
-        mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
-    }
-
-    // TODO(b/293587049): Remove after pointer icon refactor is complete.
-    private int getVirtualMousePointerDisplayId() {
-        if (com.android.input.flags.Flags.enablePointerChoreographer()) {
-            // We no longer need to get the pointer display when pointer choreographer is enabled.
-            return Display.INVALID_DISPLAY;
-        }
-        return mInputManagerInternal.getVirtualMousePointerDisplayId();
-    }
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index 340bc32..caa877c 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -81,9 +81,6 @@
       "name": "CtsPermissionTestCases",
       "options": [
         {
-          "include-filter": "android.permissionmultidevice.cts.DeviceAwarePermissionGrantTest"
-        },
-        {
           "include-filter": "android.permission.cts.DevicePermissionsTest"
         },
         {
@@ -93,6 +90,14 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
+    },
+    {
+      "name": "CtsPermissionMultiDeviceTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
     }
   ]
 }
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
index 9a73a2d..f5db6e9 100644
--- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -27,9 +27,9 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_POINTER;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 
-import static com.android.server.contextualsearch.flags.Flags.enableExcludePersistentUi;
 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT;
 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE;
 
@@ -286,13 +286,11 @@
         }
         final ScreenCapture.ScreenshotHardwareBuffer shb;
         if (mWmInternal != null) {
-            if (enableExcludePersistentUi()) {
-                shb = mWmInternal.takeAssistScreenshot(
-                        Set.of(TYPE_STATUS_BAR, TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL));
-            } else {
-                shb = mWmInternal.takeAssistScreenshot(/* windowTypesToExclude= */ Set.of());
-            }
-
+            shb = mWmInternal.takeAssistScreenshot(Set.of(
+                    TYPE_STATUS_BAR,
+                    TYPE_NAVIGATION_BAR,
+                    TYPE_NAVIGATION_BAR_PANEL,
+                    TYPE_POINTER));
         } else {
             shb = null;
         }
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 3ff0504..0fdf6d0 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -232,6 +232,7 @@
         "android.hardware.rebootescrow-V1-java",
         "android.hardware.power.stats-V2-java",
         "android.hidl.manager-V1.2-java",
+        "audio-permission-aidl-java",
         "cbor-java",
         "com.android.media.audio-aconfig-java",
         "icu4j_calendar_astronomer",
@@ -258,6 +259,7 @@
         "core_os_flags_lib",
         "connectivity_flags_lib",
         "dreams_flags_lib",
+        "aconfig_new_storage_flags_lib",
     ],
     javac_shard_size: 50,
     javacflags: [
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 748253f..1a8c3b0 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -35,7 +35,6 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
-import android.os.Bundle;
 import android.os.BundleMerger;
 import android.os.Debug;
 import android.os.DropBoxManager;
@@ -176,6 +175,16 @@
         }
     };
 
+    private static final BundleMerger sDropboxEntryAddedExtrasMerger;
+    static {
+        sDropboxEntryAddedExtrasMerger = new BundleMerger();
+        sDropboxEntryAddedExtrasMerger.setDefaultMergeStrategy(BundleMerger.STRATEGY_FIRST);
+        sDropboxEntryAddedExtrasMerger.setMergeStrategy(DropBoxManager.EXTRA_TIME,
+                BundleMerger.STRATEGY_COMPARABLE_MAX);
+        sDropboxEntryAddedExtrasMerger.setMergeStrategy(DropBoxManager.EXTRA_DROPPED_COUNT,
+                BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD);
+    }
+
     private final IDropBoxManagerService.Stub mStub = new IDropBoxManagerService.Stub() {
         @Override
         public void addData(String tag, byte[] data, int flags) {
@@ -284,7 +293,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_SEND_BROADCAST:
-                    prepareAndSendBroadcast((Intent) msg.obj, null);
+                    prepareAndSendBroadcast((Intent) msg.obj, false);
                     break;
                 case MSG_SEND_DEFERRED_BROADCAST:
                     Intent deferredIntent;
@@ -292,31 +301,42 @@
                         deferredIntent = mDeferredMap.remove((String) msg.obj);
                     }
                     if (deferredIntent != null) {
-                        prepareAndSendBroadcast(deferredIntent,
-                                createBroadcastOptions(deferredIntent));
+                        prepareAndSendBroadcast(deferredIntent, true);
                     }
                     break;
             }
         }
 
-        private void prepareAndSendBroadcast(Intent intent, Bundle options) {
+        private void prepareAndSendBroadcast(Intent intent, boolean deferrable) {
             if (!DropBoxManagerService.this.mBooted) {
                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
             }
+            final BroadcastOptions options = BroadcastOptions.makeBasic();
             if (Flags.enableReadDropboxPermission()) {
-                BroadcastOptions unbundledOptions = (options == null)
-                        ? BroadcastOptions.makeBasic() : BroadcastOptions.fromBundle(options);
-
-                unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, true);
+                options.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, true);
+                if (deferrable) {
+                    final String matchingKey = intent.getStringExtra(DropBoxManager.EXTRA_TAG)
+                            + "-READ_DROPBOX_DATA";
+                    setBroadcastOptionsForDeferral(options, matchingKey);
+                }
                 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
-                        Manifest.permission.READ_DROPBOX_DATA, unbundledOptions.toBundle());
+                        Manifest.permission.READ_DROPBOX_DATA, options.toBundle());
 
-                unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, false);
+                options.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, false);
+                if (deferrable) {
+                    final String matchingKey = intent.getStringExtra(DropBoxManager.EXTRA_TAG)
+                            + "-READ_LOGS";
+                    setBroadcastOptionsForDeferral(options, matchingKey);
+                }
                 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
-                        Manifest.permission.READ_LOGS, unbundledOptions.toBundle());
+                        Manifest.permission.READ_LOGS, options.toBundle());
             } else {
+                if (deferrable) {
+                    final String matchingKey = intent.getStringExtra(DropBoxManager.EXTRA_TAG);
+                    setBroadcastOptionsForDeferral(options, matchingKey);
+                }
                 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
-                        android.Manifest.permission.READ_LOGS, options);
+                        android.Manifest.permission.READ_LOGS, options.toBundle());
             }
         }
 
@@ -328,21 +348,12 @@
             return dropboxIntent;
         }
 
-        private Bundle createBroadcastOptions(Intent intent) {
-            final BundleMerger extrasMerger = new BundleMerger();
-            extrasMerger.setDefaultMergeStrategy(BundleMerger.STRATEGY_FIRST);
-            extrasMerger.setMergeStrategy(DropBoxManager.EXTRA_TIME,
-                    BundleMerger.STRATEGY_COMPARABLE_MAX);
-            extrasMerger.setMergeStrategy(DropBoxManager.EXTRA_DROPPED_COUNT,
-                    BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD);
-
-            return BroadcastOptions.makeBasic()
-                    .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED)
+        private void setBroadcastOptionsForDeferral(BroadcastOptions options, String matchingKey) {
+            options.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED)
                     .setDeliveryGroupMatchingKey(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED,
-                            intent.getStringExtra(DropBoxManager.EXTRA_TAG))
-                    .setDeliveryGroupExtrasMerger(extrasMerger)
-                    .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
-                    .toBundle();
+                            matchingKey)
+                    .setDeliveryGroupExtrasMerger(sDropboxEntryAddedExtrasMerger)
+                    .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
         }
 
         /**
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
index c7a8369..ef03888 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -21,6 +21,7 @@
 import static android.os.Process.SYSTEM_UID;
 
 import static com.android.server.flags.Flags.pinWebview;
+import static com.android.server.flags.Flags.skipHomeArtPins;
 
 import android.annotation.EnforcePermission;
 import android.annotation.IntDef;
@@ -121,7 +122,6 @@
             SystemProperties.getBoolean("pinner.use_pinlist", true);
 
     private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); // 80MB max for camera app.
-    private static final int MAX_HOME_PIN_SIZE = 6 * (1 << 20); // 6MB max for home app.
     private static final int MAX_ASSISTANT_PIN_SIZE = 60 * (1 << 20); // 60MB max for assistant app.
 
     public static final String ANON_REGION_STAT_NAME = "[anon]";
@@ -176,7 +176,7 @@
 
     // Resource-configured pinner flags;
     private final boolean mConfiguredToPinCamera;
-    private final boolean mConfiguredToPinHome;
+    private final int mConfiguredHomePinBytes;
     private final boolean mConfiguredToPinAssistant;
     private final int mConfiguredWebviewPinBytes;
 
@@ -238,8 +238,8 @@
         mDeviceConfigInterface = mInjector.getDeviceConfigInterface();
         mConfiguredToPinCamera = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_pinnerCameraApp);
-        mConfiguredToPinHome = context.getResources().getBoolean(
-                com.android.internal.R.bool.config_pinnerHomeApp);
+        mConfiguredHomePinBytes = context.getResources().getInteger(
+                com.android.internal.R.integer.config_pinnerHomePinBytes);
         mConfiguredToPinAssistant = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_pinnerAssistantApp);
         mConfiguredWebviewPinBytes = context.getResources().getInteger(
@@ -390,7 +390,7 @@
                     @Override
                     public void onChange(boolean selfChange, Uri uri) {
                         if (userSetupCompleteUri.equals(uri)) {
-                            if (mConfiguredToPinHome) {
+                            if (mConfiguredHomePinBytes > 0) {
                                 sendPinAppMessage(KEY_HOME, ActivityManager.getCurrentUser(),
                                         true /* force */);
                             }
@@ -594,7 +594,7 @@
             Slog.i(TAG, "Pinner - skip pinning camera app");
         }
 
-        if (mConfiguredToPinHome) {
+        if (mConfiguredHomePinBytes > 0) {
             pinKeys.add(KEY_HOME);
         }
         if (mConfiguredToPinAssistant) {
@@ -815,7 +815,7 @@
             case KEY_CAMERA:
                 return MAX_CAMERA_PIN_SIZE;
             case KEY_HOME:
-                return MAX_HOME_PIN_SIZE;
+                return mConfiguredHomePinBytes;
             case KEY_ASSISTANT:
                 return MAX_ASSISTANT_PIN_SIZE;
             default:
@@ -852,6 +852,9 @@
         }
 
         int apkPinSizeLimit = pinSizeLimit;
+
+        boolean shouldSkipArtPins = key == KEY_HOME && skipHomeArtPins();
+
         for (String apk: apks) {
             if (apkPinSizeLimit <= 0) {
                 Slog.w(TAG, "Reached to the pin size limit. Skipping: " + apk);
@@ -871,11 +874,12 @@
             }
             synchronized (this) {
                 pinnedApp.mFiles.add(pf);
+                mPinnedFiles.put(pf.fileName, pf);
             }
 
             apkPinSizeLimit -= pf.bytesPinned;
-            if (apk.equals(appInfo.sourceDir)) {
-                pinOptimizedDexDependencies(pf, apkPinSizeLimit, appInfo);
+            if (apk.equals(appInfo.sourceDir) && !shouldSkipArtPins) {
+                pinOptimizedDexDependencies(pf, Integer.MAX_VALUE, appInfo);
             }
         }
     }
@@ -921,8 +925,8 @@
         }
         pf.groupName = groupName != null ? groupName : "";
 
-        maxBytesToPin -= bytesPinned;
         bytesPinned += pf.bytesPinned;
+        maxBytesToPin -= bytesPinned;
 
         synchronized (this) {
             mPinnedFiles.put(pf.fileName, pf);
@@ -970,7 +974,7 @@
                 // Unpin if it was already pinned prior to re-pinning.
                 unpinFile(file);
 
-                PinnedFile df = mInjector.pinFileInternal(file, Integer.MAX_VALUE,
+                PinnedFile df = mInjector.pinFileInternal(file, maxBytesToPin,
                         /*attemptPinIntrospection=*/false);
                 if (df == null) {
                     Slog.i(TAG, "Failed to pin ART file = " + file);
@@ -1342,18 +1346,6 @@
 
     public List<PinnedFileStat> getPinnerStats() {
         ArrayList<PinnedFileStat> stats = new ArrayList<>();
-        Collection<PinnedApp> pinnedApps;
-        synchronized(this) {
-            pinnedApps = mPinnedApps.values();
-        }
-        for (PinnedApp pinnedApp : pinnedApps) {
-            for (PinnedFile pf : pinnedApp.mFiles) {
-                PinnedFileStat stat =
-                        new PinnedFileStat(pf.fileName, pf.bytesPinned, pf.groupName);
-                stats.add(stat);
-            }
-        }
-
         Collection<PinnedFile> pinnedFiles;
         synchronized(this) {
             pinnedFiles = mPinnedFiles.values();
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index a508ebf..8c1bb3b 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -1783,7 +1783,13 @@
                 String gidStr = parser.getAttributeValue(null, "gid");
                 if (gidStr != null) {
                     int gid = Process.getGidForName(gidStr);
-                    perm.gids = appendInt(perm.gids, gid);
+                    if (gid != -1) {
+                        perm.gids = appendInt(perm.gids, gid);
+                    } else {
+                        Slog.w(TAG, "<group> with unknown gid \""
+                                + gidStr + " for permission " + name + " in "
+                                + parser.getPositionDescription());
+                    }
                 } else {
                     Slog.w(TAG, "<group> without gid at "
                             + parser.getPositionDescription());
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 5933639..a3b6d80 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -134,6 +134,13 @@
         },
         {
             "name": "CtsSuspendAppsTestCases"
+        },
+        {
+            "name": "CtsWindowManagerBackgroundActivityTestCases",
+            "file_patterns": [
+                "Background.*\\.java",
+                "Activity.*\\.java"
+            ]
         }
     ],
     "presubmit-large": [
@@ -187,6 +194,18 @@
         },
         {
             "name": "SelinuxFrameworksTests"
+        },
+        {
+            "name": "WmTests",
+            "file_patterns": [
+                "Background.*\\.java",
+                "Activity.*\\.java"
+            ],
+            "options": [
+                {
+                    "include-filter": "com.android.server.wm.BackgroundActivityStart*"
+                }
+            ]
         }
-    ]
+   ]
 }
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index f1776f4..f1d3584 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -1986,11 +1986,9 @@
         // the status bar should be totally disabled, the calls below will
         // have no effect until the device is unlocked.
         if (mStatusBarManager != null) {
-            StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
-            if (mCarModeEnabled) {
-                info.setNotificationTickerDisabled(true);
-            }
-            mStatusBarManager.requestDisabledComponent(info, "adjustStatusBarCarModeLocked");
+            mStatusBarManager.disable(mCarModeEnabled
+                    ? StatusBarManager.DISABLE_NOTIFICATION_TICKER
+                    : StatusBarManager.DISABLE_NONE);
         }
 
         if (mNotificationManager == null) {
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 7acca19..e2b6bd6 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -36,6 +36,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.AppOpsManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -47,6 +48,7 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
+import android.net.vcn.Flags;
 import android.net.vcn.IVcnManagementService;
 import android.net.vcn.IVcnStatusCallback;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
@@ -68,6 +70,7 @@
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -431,6 +434,8 @@
         mTelephonySubscriptionTracker.register();
     }
 
+    // The system server automatically has the required permissions for #getMainUser()
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     private void enforcePrimaryUser() {
         final int uid = mDeps.getBinderCallingUid();
         if (uid == Process.SYSTEM_UID) {
@@ -438,7 +443,20 @@
                     "Calling identity was System Server. Was Binder calling identity cleared?");
         }
 
-        if (!UserHandle.getUserHandleForUid(uid).isSystem()) {
+        final UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
+
+        if (Flags.enforceMainUser()) {
+            final UserManager userManager = mContext.getSystemService(UserManager.class);
+
+            Binder.withCleanCallingIdentity(
+                    () -> {
+                        if (!Objects.equals(userManager.getMainUser(), userHandle)) {
+                            throw new SecurityException(
+                                    "VcnManagementService can only be used by callers running as"
+                                            + " the main user");
+                        }
+                    });
+        } else if (!userHandle.isSystem()) {
             throw new SecurityException(
                     "VcnManagementService can only be used by callers running as the primary user");
         }
diff --git a/services/core/java/com/android/server/WallpaperUpdateReceiver.java b/services/core/java/com/android/server/WallpaperUpdateReceiver.java
index 2812233..42391a5 100644
--- a/services/core/java/com/android/server/WallpaperUpdateReceiver.java
+++ b/services/core/java/com/android/server/WallpaperUpdateReceiver.java
@@ -24,7 +24,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.Bitmap;
 import android.os.AsyncTask;
 import android.os.ParcelFileDescriptor;
 import android.util.Slog;
@@ -59,10 +58,10 @@
                 return;
             }
             if (DEBUG) Slog.d(TAG, "Set customized default_wallpaper.");
-            Bitmap blank = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
-            // set a blank wallpaper to force a redraw of default_wallpaper
-            wallpaperManager.setBitmap(blank);
-            wallpaperManager.setResource(com.android.internal.R.drawable.default_wallpaper);
+            // Check if it is not a live wallpaper set
+            if (wallpaperManager.getWallpaperInfo() == null) {
+                wallpaperManager.clearWallpaper();
+            }
         } catch (Exception e) {
             Slog.w(TAG, "Failed to customize system wallpaper." + e);
         }
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 1015ad9..ac9ed0d 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -4532,8 +4532,13 @@
     public Account[] getAccountsAsUser(String type, int userId, String opPackageName) {
         int callingUid = Binder.getCallingUid();
         mAppOpsManager.checkPackage(callingUid, opPackageName);
-        return getAccountsAsUserForPackage(type, userId, opPackageName /* callingPackage */, -1,
-                opPackageName, false /* includeUserManagedNotVisible */);
+        try {
+            return getAccountsAsUserForPackage(type, userId, opPackageName /* callingPackage */, -1,
+                    opPackageName, false /* includeUserManagedNotVisible */);
+        } catch (SQLiteCantOpenDatabaseException e) {
+            Log.e(TAG, "Could not get accounts for user " + userId, e);
+            return new Account[]{};
+        }
     }
 
     @NonNull
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 4ca9e33..9be0e1f 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -157,6 +157,7 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException;
+import android.app.RemoteServiceException.ForegroundServiceDidNotStopInTimeException;
 import android.app.Service;
 import android.app.ServiceStartArgs;
 import android.app.StartForegroundCalledOnStoppedServiceException;
@@ -784,7 +785,7 @@
                 ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG,
                 "SERVICE_FOREGROUND_TIMEOUT");
         this.mFGSAnrTimer = new ServiceAnrTimer(service,
-                ActivityManagerService.SERVICE_FGS_ANR_TIMEOUT_MSG,
+                ActivityManagerService.SERVICE_FGS_CRASH_TIMEOUT_MSG,
                 "FGS_TIMEOUT");
     }
 
@@ -1168,9 +1169,7 @@
     }
 
     private boolean shouldAllowBootCompletedStart(ServiceRecord r, int foregroundServiceType) {
-        @PowerExemptionManager.ReasonCode final int fgsStartReasonCode =
-                r.mInfoTempFgsAllowListReason != null ? r.mInfoTempFgsAllowListReason.mReasonCode
-                                                      : REASON_DENIED;
+        @PowerExemptionManager.ReasonCode final int fgsStartReasonCode = r.getFgsAllowStart();
         if (Flags.fgsBootCompleted()
                 && CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, r.appInfo.uid)
                 && fgsStartReasonCode == PowerExemptionManager.REASON_BOOT_COMPLETED) {
@@ -2454,10 +2453,21 @@
                                 } else if (lastTimeOutAt > 0) {
                                     // Time limit was exhausted within the past 24 hours and the app
                                     // has not been in the TOP state since then, throw an exception.
-                                    throw new ForegroundServiceStartNotAllowedException("Time limit"
-                                            + " already exhausted for foreground service type "
+                                    final String exceptionMsg = "Time limit already exhausted for"
+                                            + " foreground service type "
                                             + ServiceInfo.foregroundServiceTypeToLabel(
-                                                            foregroundServiceType));
+                                                    foregroundServiceType);
+                                    // Only throw an exception if the new ANR behavior
+                                    // ("do nothing") is not gated or the new crashing logic gate
+                                    // is enabled; otherwise, reset the limit temporarily.
+                                    if (!android.app.Flags.gateFgsTimeoutAnrBehavior()
+                                            || android.app.Flags.enableFgsTimeoutCrashBehavior()) {
+                                        throw new ForegroundServiceStartNotAllowedException(
+                                                    exceptionMsg);
+                                    } else {
+                                        Slog.wtf(TAG, exceptionMsg);
+                                        fgsTypeInfo.reset();
+                                    }
                                 }
                             }
                         } else {
@@ -3773,6 +3783,14 @@
         return fgsInfo.getLastFgsStartTime() + Math.max(0, timeLimit - fgsInfo.getTotalRuntime());
     }
 
+    private TimeLimitedFgsInfo getFgsTimeLimitedInfo(int uid, int fgsType) {
+        final SparseArray<TimeLimitedFgsInfo> fgsInfo = mTimeLimitedFgsInfo.get(uid);
+        if (fgsInfo != null) {
+            return fgsInfo.get(fgsType);
+        }
+        return null;
+    }
+
     private void maybeUpdateFgsTrackingLocked(ServiceRecord sr, int previousFgsType) {
         final int previouslyTimeLimitedType = getTimeLimitedFgsType(previousFgsType);
         if (previouslyTimeLimitedType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE
@@ -3783,16 +3801,12 @@
 
         if (previouslyTimeLimitedType != ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
             // FGS is switching types and the previous type was time-limited so update the runtime.
-            final SparseArray<TimeLimitedFgsInfo> fgsInfo = mTimeLimitedFgsInfo.get(sr.appInfo.uid);
-            if (fgsInfo != null) {
-                final TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(previouslyTimeLimitedType);
-                if (fgsTypeInfo != null) {
-                    // Update the total runtime for the previous time-limited fgs type.
-                    fgsTypeInfo.updateTotalRuntime();
-                    // TODO(b/330399444): handle the case where an app is running 2 services of the
-                    //  same time-limited type in parallel and stops one of them which leads to the
-                    //  second running one gaining additional runtime.
-                }
+            final TimeLimitedFgsInfo fgsTypeInfo = getFgsTimeLimitedInfo(
+                                                    sr.appInfo.uid, previouslyTimeLimitedType);
+            if (fgsTypeInfo != null) {
+                // Update the total runtime for the previous time-limited fgs type.
+                fgsTypeInfo.updateTotalRuntime(SystemClock.uptimeMillis());
+                fgsTypeInfo.decNumParallelServices();
             }
 
             if (!sr.isFgsTimeLimited()) {
@@ -3815,10 +3829,10 @@
         final int timeLimitedFgsType = getTimeLimitedFgsType(sr.foregroundServiceType);
         TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(timeLimitedFgsType);
         if (fgsTypeInfo == null) {
-            fgsTypeInfo = sr.createTimeLimitedFgsInfo(nowUptime);
+            fgsTypeInfo = sr.createTimeLimitedFgsInfo();
             fgsInfo.put(timeLimitedFgsType, fgsTypeInfo);
         }
-        fgsTypeInfo.setLastFgsStartTime(nowUptime);
+        fgsTypeInfo.noteFgsFgsStart(nowUptime);
 
         // We'll cancel the previous ANR timer and start a fresh one below.
         mFGSAnrTimer.cancel(sr);
@@ -3842,13 +3856,12 @@
             return; // if the current fgs type is not time-limited, return.
         }
 
-        final SparseArray<TimeLimitedFgsInfo> fgsInfo = mTimeLimitedFgsInfo.get(sr.appInfo.uid);
-        if (fgsInfo != null) {
-            final TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(timeLimitedType);
-            if (fgsTypeInfo != null) {
-                // Update the total runtime for the previous time-limited fgs type.
-                fgsTypeInfo.updateTotalRuntime();
-            }
+        final TimeLimitedFgsInfo fgsTypeInfo = getFgsTimeLimitedInfo(
+                                                sr.appInfo.uid, timeLimitedType);
+        if (fgsTypeInfo != null) {
+            // Update the total runtime for the previous time-limited fgs type.
+            fgsTypeInfo.updateTotalRuntime(SystemClock.uptimeMillis());
+            fgsTypeInfo.decNumParallelServices();
         }
         Slog.d(TAG_SERVICE, "Stop FGS timeout: " + sr);
         mFGSAnrTimer.cancel(sr);
@@ -3903,24 +3916,21 @@
             mFGSAnrTimer.accept(sr);
             traceInstant("FGS timed out: ", sr);
 
-            final SparseArray<TimeLimitedFgsInfo> fgsInfo = mTimeLimitedFgsInfo.get(sr.appInfo.uid);
-            if (fgsInfo != null) {
-                final TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(fgsType);
-                if (fgsTypeInfo != null) {
-                    // Update total runtime for the time-limited fgs type and mark it as timed out.
-                    fgsTypeInfo.updateTotalRuntime();
-                    fgsTypeInfo.setTimeLimitExceededAt(nowUptime);
+            final TimeLimitedFgsInfo fgsTypeInfo = getFgsTimeLimitedInfo(sr.appInfo.uid, fgsType);
+            if (fgsTypeInfo != null) {
+                // Update total runtime for the time-limited fgs type and mark it as timed out.
+                fgsTypeInfo.updateTotalRuntime(nowUptime);
+                fgsTypeInfo.setTimeLimitExceededAt(nowUptime);
 
-                    logFGSStateChangeLocked(sr,
-                            FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT,
-                            nowUptime > fgsTypeInfo.getLastFgsStartTime()
-                                    ? (int) (nowUptime - fgsTypeInfo.getLastFgsStartTime()) : 0,
-                            FGS_STOP_REASON_UNKNOWN,
-                            FGS_TYPE_POLICY_CHECK_UNKNOWN,
-                            FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA,
-                            false /* fgsRestrictionRecalculated */
-                    );
-                }
+                logFGSStateChangeLocked(sr,
+                        FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT,
+                        nowUptime > fgsTypeInfo.getFirstFgsStartUptime()
+                                ? (int) (nowUptime - fgsTypeInfo.getFirstFgsStartUptime()) : 0,
+                        FGS_STOP_REASON_UNKNOWN,
+                        FGS_TYPE_POLICY_CHECK_UNKNOWN,
+                        FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA,
+                        false /* fgsRestrictionRecalculated */
+                );
             }
 
             try {
@@ -3929,34 +3939,65 @@
                 Slog.w(TAG_SERVICE, "Exception from scheduleTimeoutServiceForType: " + e);
             }
 
-            // ANR the service after giving the service some time to clean up.
-            mFGSAnrTimer.start(sr, mAm.mConstants.mFgsAnrExtraWaitDuration);
+            // Crash the service after giving the service some time to clean up.
+            mFGSAnrTimer.start(sr, mAm.mConstants.mFgsCrashExtraWaitDuration);
         }
     }
 
-    void onFgsAnrTimeout(ServiceRecord sr) {
+    void onFgsCrashTimeout(ServiceRecord sr) {
         final int fgsType = getTimeLimitedFgsType(sr.foregroundServiceType);
         if (fgsType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
             return; // no timed out FGS type was found (either it was stopped or it switched types)
         }
+
+        synchronized (mAm) {
+            final TimeLimitedFgsInfo fgsTypeInfo = getFgsTimeLimitedInfo(sr.appInfo.uid, fgsType);
+            if (fgsTypeInfo != null) {
+                // Runtime is already updated when the service times out - if the app didn't
+                // stop the service, decrement the number of parallel running services here.
+                fgsTypeInfo.decNumParallelServices();
+            }
+        }
+
         final String reason = "A foreground service of type "
                 + ServiceInfo.foregroundServiceTypeToLabel(fgsType)
                 + " did not stop within its timeout: " + sr.getComponentName();
 
-        final TimeoutRecord tr = TimeoutRecord.forFgsTimeout(reason);
-        tr.mLatencyTracker.waitingOnAMSLockStarted();
-        synchronized (mAm) {
-            tr.mLatencyTracker.waitingOnAMSLockEnded();
-
-            Slog.e(TAG_SERVICE, "FGS ANR'ed: " + sr);
-            traceInstant("FGS ANR: ", sr);
-            if (sr.app != null) {
-                mAm.appNotResponding(sr.app, tr);
+        if (android.app.Flags.gateFgsTimeoutAnrBehavior()) {
+            // Log a WTF instead of throwing an ANR while the new behavior is gated.
+            Slog.wtf(TAG, reason);
+            return;
+        }
+        if (android.app.Flags.enableFgsTimeoutCrashBehavior()) {
+            // Crash the app
+            synchronized (mAm) {
+                Slog.e(TAG_SERVICE, "FGS Crashed: " + sr);
+                traceInstant("FGS Crash: ", sr);
+                if (sr.app != null) {
+                    mAm.crashApplicationWithTypeWithExtras(sr.app.uid, sr.app.getPid(),
+                            sr.app.info.packageName, sr.app.userId, reason, false /*force*/,
+                            ForegroundServiceDidNotStopInTimeException.TYPE_ID,
+                            ForegroundServiceDidNotStopInTimeException
+                                    .createExtrasForService(sr.getComponentName()));
+                }
             }
+        } else {
+            // ANR the app if the new crash behavior is not enabled
+            final TimeoutRecord tr = TimeoutRecord.forFgsTimeout(reason);
+            tr.mLatencyTracker.waitingOnAMSLockStarted();
+            synchronized (mAm) {
+                tr.mLatencyTracker.waitingOnAMSLockEnded();
 
-            // TODO: Can we close the ANR dialog here, if it's still shown? Currently, the ANR
-            // dialog really doesn't remember the "cause" (especially if there have been multiple
-            // ANRs), so it's not doable.
+                Slog.e(TAG_SERVICE, "FGS ANR'ed: " + sr);
+                traceInstant("FGS ANR: ", sr);
+                if (sr.app != null) {
+                    mAm.appNotResponding(sr.app, tr);
+                }
+
+                // TODO: Can we close the ANR dialog here, if it's still shown? Currently, the ANR
+                // dialog really doesn't remember the "cause" (especially if there have been
+                // multiple ANRs), so it's not doable.
+            }
         }
     }
 
@@ -3973,7 +4014,6 @@
 
     private void stopServiceAndUpdateAllowlistManagerLocked(ServiceRecord service) {
         maybeStopShortFgsTimeoutLocked(service);
-        maybeStopFgsTimeoutLocked(service);
         final ProcessServiceRecord psr = service.app.mServices;
         psr.stopService(service);
         psr.updateBoundClientUids();
@@ -6259,7 +6299,6 @@
             Slog.w(TAG_SERVICE, "Short FGS brought down without stopping: " + r);
             maybeStopShortFgsTimeoutLocked(r);
         }
-        maybeStopFgsTimeoutLocked(r);
 
         // Report to all of the connections that the service is no longer
         // available.
@@ -6384,7 +6423,6 @@
         final boolean exitingFg = r.isForeground;
         if (exitingFg) {
             maybeStopShortFgsTimeoutLocked(r);
-            maybeStopFgsTimeoutLocked(r);
             decActiveForegroundAppLocked(smap, r);
             synchronized (mAm.mProcessStats.mLock) {
                 ServiceState stracker = r.getTracker();
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 9e06b75..26aa053 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -1115,17 +1115,17 @@
 
     /**
      * If a service of a timeout-enforced type doesn't finish within this duration after its
-     * timeout, then we'll declare an ANR.
+     * timeout, then we'll crash the app.
      * i.e. if the time limit for a type is 1 hour, and this extra duration is 10 seconds, then
-     * the app will be ANR'ed 1 hour and 10 seconds after it started.
+     * the app will crash 1 hour and 10 seconds after it started.
      */
-    private static final String KEY_FGS_ANR_EXTRA_WAIT_DURATION = "fgs_anr_extra_wait_duration";
+    private static final String KEY_FGS_CRASH_EXTRA_WAIT_DURATION = "fgs_crash_extra_wait_duration";
 
-    /** @see #KEY_FGS_ANR_EXTRA_WAIT_DURATION */
-    static final long DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION = 10_000;
+    /** @see #KEY_FGS_CRASH_EXTRA_WAIT_DURATION */
+    static final long DEFAULT_FGS_CRASH_EXTRA_WAIT_DURATION = 10_000;
 
-    /** @see #KEY_FGS_ANR_EXTRA_WAIT_DURATION */
-    public volatile long mFgsAnrExtraWaitDuration = DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION;
+    /** @see #KEY_FGS_CRASH_EXTRA_WAIT_DURATION */
+    public volatile long mFgsCrashExtraWaitDuration = DEFAULT_FGS_CRASH_EXTRA_WAIT_DURATION;
 
     /** @see #KEY_USE_TIERED_CACHED_ADJ */
     public boolean USE_TIERED_CACHED_ADJ = DEFAULT_USE_TIERED_CACHED_ADJ;
@@ -1315,8 +1315,8 @@
                             case KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION:
                                 updateShortFgsAnrExtraWaitDuration();
                                 break;
-                            case KEY_FGS_ANR_EXTRA_WAIT_DURATION:
-                                updateFgsAnrExtraWaitDuration();
+                            case KEY_FGS_CRASH_EXTRA_WAIT_DURATION:
+                                updateFgsCrashExtraWaitDuration();
                                 break;
                             case KEY_PROACTIVE_KILLS_ENABLED:
                                 updateProactiveKillsEnabled();
@@ -2199,11 +2199,11 @@
                 DEFAULT_DATA_SYNC_FGS_TIMEOUT_DURATION);
     }
 
-    private void updateFgsAnrExtraWaitDuration() {
-        mFgsAnrExtraWaitDuration = DeviceConfig.getLong(
+    private void updateFgsCrashExtraWaitDuration() {
+        mFgsCrashExtraWaitDuration = DeviceConfig.getLong(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                KEY_FGS_ANR_EXTRA_WAIT_DURATION,
-                DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION);
+                KEY_FGS_CRASH_EXTRA_WAIT_DURATION,
+                DEFAULT_FGS_CRASH_EXTRA_WAIT_DURATION);
     }
 
     private void updateEnableWaitForFinishAttachApplication() {
@@ -2456,8 +2456,8 @@
         pw.print("="); pw.println(mMediaProcessingFgsTimeoutDuration);
         pw.print("  "); pw.print(KEY_DATA_SYNC_FGS_TIMEOUT_DURATION);
         pw.print("="); pw.println(mDataSyncFgsTimeoutDuration);
-        pw.print("  "); pw.print(KEY_FGS_ANR_EXTRA_WAIT_DURATION);
-        pw.print("="); pw.println(mFgsAnrExtraWaitDuration);
+        pw.print("  "); pw.print(KEY_FGS_CRASH_EXTRA_WAIT_DURATION);
+        pw.print("="); pw.println(mFgsCrashExtraWaitDuration);
 
         pw.print("  "); pw.print(KEY_USE_TIERED_CACHED_ADJ);
         pw.print("="); pw.println(USE_TIERED_CACHED_ADJ);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1b3b198..316937c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -598,6 +598,9 @@
     // as one line, but close enough for now.
     static final int RESERVED_BYTES_PER_LOGCAT_LINE = 100;
 
+    // How many seconds should the system wait before terminating the spawned logcat process.
+    static final int LOGCAT_TIMEOUT_SEC = 10;
+
     // Necessary ApplicationInfo flags to mark an app as persistent
     static final int PERSISTENT_MASK =
             ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT;
@@ -1670,7 +1673,7 @@
     static final int BIND_APPLICATION_TIMEOUT_SOFT_MSG = 82;
     static final int BIND_APPLICATION_TIMEOUT_HARD_MSG = 83;
     static final int SERVICE_FGS_TIMEOUT_MSG = 84;
-    static final int SERVICE_FGS_ANR_TIMEOUT_MSG = 85;
+    static final int SERVICE_FGS_CRASH_TIMEOUT_MSG = 85;
 
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
 
@@ -2041,8 +2044,8 @@
                 case SERVICE_FGS_TIMEOUT_MSG: {
                     mServices.onFgsTimeout((ServiceRecord) msg.obj);
                 } break;
-                case SERVICE_FGS_ANR_TIMEOUT_MSG: {
-                    mServices.onFgsAnrTimeout((ServiceRecord) msg.obj);
+                case SERVICE_FGS_CRASH_TIMEOUT_MSG: {
+                    mServices.onFgsCrashTimeout((ServiceRecord) msg.obj);
                 } break;
             }
         }
@@ -4781,7 +4784,7 @@
 
             checkTime(startTime, "attachApplicationLocked: immediately before bindApplication");
             bindApplicationTimeMillis = SystemClock.uptimeMillis();
-            bindApplicationTimeNanos = SystemClock.elapsedRealtimeNanos();
+            bindApplicationTimeNanos = SystemClock.uptimeNanos();
             mAtmInternal.preBindApplication(app.getWindowProcessController());
             final ActiveInstrumentation instr2 = app.getActiveInstrumentation();
             if (mPlatformCompat != null) {
@@ -4851,7 +4854,8 @@
 
             app.setBindApplicationTime(bindApplicationTimeMillis);
             mProcessList.getAppStartInfoTracker()
-                    .reportBindApplicationTimeNanos(app, bindApplicationTimeNanos);
+                    .addTimestampToStart(app, bindApplicationTimeNanos,
+                            ApplicationStartInfo.START_TIMESTAMP_BIND_APPLICATION);
 
             // Make app active after binding application or client may be running requests (e.g
             // starting activities) before it is ready.
@@ -9938,126 +9942,70 @@
         // If process is null, we are being called from some internal code
         // and may be about to die -- run this synchronously.
         final boolean runSynchronously = process == null;
-        Thread worker =
-                new Thread("Error dump: " + dropboxTag) {
-                    @Override
-                    public void run() {
-                        if (report != null) {
-                            sb.append(report);
+        Thread worker = new Thread("Error dump: " + dropboxTag) {
+            @Override
+            public void run() {
+                if (report != null) {
+                    sb.append(report);
+                }
+
+                String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
+                String kerLogSetting = Settings.Global.ERROR_KERNEL_LOG_PREFIX + dropboxTag;
+                String maxBytesSetting = Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag;
+                int logcatLines = Build.IS_USER
+                        ? 0
+                        : Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0);
+                int kernelLogLines = Build.IS_USER
+                        ? 0
+                        : Settings.Global.getInt(mContext.getContentResolver(), kerLogSetting, 0);
+                int dropboxMaxSize = Settings.Global.getInt(
+                        mContext.getContentResolver(), maxBytesSetting, DROPBOX_DEFAULT_MAX_SIZE);
+
+                if (dataFile != null) {
+                    // Attach the stack traces file to the report so collectors can load them
+                    // by file if they have access.
+                    sb.append(DATA_FILE_PATH_HEADER)
+                            .append(dataFile.getAbsolutePath()).append('\n');
+
+                    int maxDataFileSize = dropboxMaxSize
+                            - sb.length()
+                            - logcatLines * RESERVED_BYTES_PER_LOGCAT_LINE
+                            - kernelLogLines * RESERVED_BYTES_PER_LOGCAT_LINE
+                            - DATA_FILE_PATH_FOOTER.length();
+
+                    if (maxDataFileSize > 0) {
+                        // Inline dataFile contents if there is room.
+                        try {
+                            sb.append(FileUtils.readTextFile(dataFile, maxDataFileSize,
+                                    "\n\n[[TRUNCATED]]\n"));
+                        } catch (IOException e) {
+                            Slog.e(TAG, "Error reading " + dataFile, e);
                         }
-
-                        String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
-                        String maxBytesSetting =
-                                Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag;
-                        int lines =
-                                Build.IS_USER
-                                        ? 0
-                                        : Settings.Global.getInt(
-                                                mContext.getContentResolver(), logcatSetting, 0);
-                        int dropboxMaxSize =
-                                Settings.Global.getInt(
-                                        mContext.getContentResolver(),
-                                        maxBytesSetting,
-                                        DROPBOX_DEFAULT_MAX_SIZE);
-
-                        if (dataFile != null) {
-                            // Attach the stack traces file to the report so collectors can load
-                            // them
-                            // by file if they have access.
-                            sb.append(DATA_FILE_PATH_HEADER)
-                                    .append(dataFile.getAbsolutePath())
-                                    .append('\n');
-
-                            int maxDataFileSize =
-                                    dropboxMaxSize
-                                            - sb.length()
-                                            - lines * RESERVED_BYTES_PER_LOGCAT_LINE
-                                            - DATA_FILE_PATH_FOOTER.length();
-
-                            if (maxDataFileSize > 0) {
-                                // Inline dataFile contents if there is room.
-                                try {
-                                    sb.append(
-                                            FileUtils.readTextFile(
-                                                    dataFile,
-                                                    maxDataFileSize,
-                                                    "\n\n[[TRUNCATED]]\n"));
-                                } catch (IOException e) {
-                                    Slog.e(TAG, "Error reading " + dataFile, e);
-                                }
-                            }
-
-                            // Always append the footer, even there wasn't enough space to inline
-                            // the
-                            // dataFile contents.
-                            sb.append(DATA_FILE_PATH_FOOTER);
-                        }
-
-                        if (crashInfo != null && crashInfo.stackTrace != null) {
-                            sb.append(crashInfo.stackTrace);
-                        }
-
-                        if (lines > 0 && !runSynchronously) {
-                            sb.append("\n");
-
-                            InputStreamReader input = null;
-                            try {
-                                java.lang.Process logcat =
-                                        new ProcessBuilder(
-                                                        // Time out after 10s of inactivity, but
-                                                        // kill logcat with SEGV
-                                                        // so we can investigate why it didn't
-                                                        // finish.
-                                                        "/system/bin/timeout",
-                                                        "-i",
-                                                        "-s",
-                                                        "SEGV",
-                                                        "10s",
-                                                        // Merge several logcat streams, and take
-                                                        // the last N lines.
-                                                        "/system/bin/logcat",
-                                                        "-v",
-                                                        "threadtime",
-                                                        "-b",
-                                                        "events",
-                                                        "-b",
-                                                        "system",
-                                                        "-b",
-                                                        "main",
-                                                        "-b",
-                                                        "crash",
-                                                        "-t",
-                                                        String.valueOf(lines))
-                                                .redirectErrorStream(true)
-                                                .start();
-
-                                try {
-                                    logcat.getOutputStream().close();
-                                } catch (IOException e) {
-                                }
-                                try {
-                                    logcat.getErrorStream().close();
-                                } catch (IOException e) {
-                                }
-                                input = new InputStreamReader(logcat.getInputStream());
-
-                                int num;
-                                char[] buf = new char[8192];
-                                while ((num = input.read(buf)) > 0) sb.append(buf, 0, num);
-                            } catch (IOException e) {
-                                Slog.e(TAG, "Error running logcat", e);
-                            } finally {
-                                if (input != null)
-                                    try {
-                                        input.close();
-                                    } catch (IOException e) {
-                                    }
-                            }
-                        }
-
-                        dbox.addText(dropboxTag, sb.toString());
                     }
-                };
+                    // Always append the footer, even there wasn't enough space to inline the
+                    // dataFile contents.
+                    sb.append(DATA_FILE_PATH_FOOTER);
+                }
+
+                if (crashInfo != null && crashInfo.stackTrace != null) {
+                    sb.append(crashInfo.stackTrace);
+                }
+                boolean shouldAddLogs = logcatLines > 0 || kernelLogLines > 0;
+                if (!runSynchronously && shouldAddLogs) {
+                    sb.append("\n");
+                    if (logcatLines > 0) {
+                        fetchLogcatBuffers(sb, logcatLines, LOGCAT_TIMEOUT_SEC,
+                                List.of("events", "system", "main", "crash"));
+                    }
+                    if (kernelLogLines > 0) {
+                        fetchLogcatBuffers(sb, kernelLogLines, LOGCAT_TIMEOUT_SEC / 2,
+                                List.of("kernel"));
+                    }
+                }
+
+                dbox.addText(dropboxTag, sb.toString());
+            }
+        };
 
         if (runSynchronously) {
             final int oldMask = StrictMode.allowThreadDiskWritesMask();
@@ -10320,6 +10268,67 @@
     }
 
     /**
+     * Retrieves logs from specified logcat buffers and appends them to a StringBuilder
+     * in the supplied order. The method executes a logcat command to fetch specific
+     * log entries from the supplied buffers.
+     *
+     * @param sb the StringBuilder to append the logcat output to.
+     * @param lines the number of lines to retrieve.
+     * @param timeout the maximum allowed time in seconds for logcat to run before being terminated.
+     * @param buffers the list of log buffers from which to retrieve logs.
+     */
+    private static void fetchLogcatBuffers(StringBuilder sb, int lines,
+            int timeout, List<String> buffers) {
+
+        if (buffers.size() == 0 || lines <= 0 || timeout <= 0) {
+            return;
+        }
+
+        List<String> command = new ArrayList<>(10 + (2 * buffers.size()));
+        // Time out after 10s of inactivity, but kill logcat with SEGV
+        // so we can investigate why it didn't finish.
+        command.add("/system/bin/timeout");
+        command.add("-i");
+        command.add("-s");
+        command.add("SEGV");
+        command.add(timeout + "s");
+
+        // Merge several logcat streams, and take the last N lines.
+        command.add("/system/bin/logcat");
+        command.add("-v");
+        // This adds a timestamp and thread info to each log line.
+        command.add("threadtime");
+        for (String buffer : buffers) {
+            command.add("-b");
+            command.add(buffer);
+        }
+        // Limit the output to the last N lines.
+        command.add("-t");
+        command.add(String.valueOf(lines));
+
+        try {
+            java.lang.Process proc =
+                    new ProcessBuilder(command).redirectErrorStream(true).start();
+
+            // Close the output stream immediately as we do not send input to the process.
+            try {
+                proc.getOutputStream().close();
+            } catch (IOException e) {
+            }
+
+            try (InputStreamReader reader = new InputStreamReader(proc.getInputStream())) {
+                char[] buffer = new char[8192];
+                int numRead;
+                while ((numRead = reader.read(buffer, 0, buffer.length)) > 0) {
+                    sb.append(buffer, 0, numRead);
+                }
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "Error running logcat", e);
+        }
+    }
+
+    /**
      * Check if the calling process has the permission to dump given package,
      * throw SecurityException if it doesn't have the permission.
      *
@@ -12325,8 +12334,8 @@
             ProcessList.SYSTEM_ADJ, ProcessList.PERSISTENT_PROC_ADJ,
             ProcessList.PERSISTENT_SERVICE_ADJ, ProcessList.FOREGROUND_APP_ADJ,
             ProcessList.VISIBLE_APP_ADJ,
-            ProcessList.PERCEPTIBLE_APP_ADJ, ProcessList.PERCEPTIBLE_LOW_APP_ADJ,
-            ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ,
+            ProcessList.PERCEPTIBLE_APP_ADJ,
+            ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ, ProcessList.PERCEPTIBLE_LOW_APP_ADJ,
             ProcessList.BACKUP_APP_ADJ, ProcessList.HEAVY_WEIGHT_APP_ADJ,
             ProcessList.SERVICE_ADJ, ProcessList.HOME_APP_ADJ,
             ProcessList.PREVIOUS_APP_ADJ, ProcessList.SERVICE_B_ADJ, ProcessList.CACHED_APP_MIN_ADJ
@@ -12334,7 +12343,7 @@
     static final String[] DUMP_MEM_OOM_LABEL = new String[] {
             "Native",
             "System", "Persistent", "Persistent Service", "Foreground",
-            "Visible", "Perceptible", "Perceptible Low", "Perceptible Medium",
+            "Visible", "Perceptible", "Perceptible Medium", "Perceptible Low",
             "Backup", "Heavy Weight",
             "A Services", "Home",
             "Previous", "B Services", "Cached"
@@ -12342,7 +12351,7 @@
     static final String[] DUMP_MEM_OOM_COMPACT_LABEL = new String[] {
             "native",
             "sys", "pers", "persvc", "fore",
-            "vis", "percept", "perceptl", "perceptm",
+            "vis", "percept", "perceptm", "perceptl",
             "backup", "heavy",
             "servicea", "home",
             "prev", "serviceb", "cached"
@@ -14317,7 +14326,7 @@
     // activity manager to announce its creation.
     public boolean bindBackupAgent(String packageName, int backupMode, int targetUserId,
             @BackupDestination int backupDestination) {
-        long startTimeNs = SystemClock.elapsedRealtimeNanos();
+        long startTimeNs = SystemClock.uptimeNanos();
         if (DEBUG_BACKUP) {
             Slog.v(TAG, "bindBackupAgent: app=" + packageName + " mode=" + backupMode
                     + " targetUserId=" + targetUserId + " callingUid = " + Binder.getCallingUid()
@@ -19416,7 +19425,6 @@
                     // If the process is known as top app, set a hint so when the process is
                     // started, the top priority can be applied immediately to avoid cpu being
                     // preempted by other processes before attaching the process of top app.
-                    final long startTimeNs = SystemClock.elapsedRealtimeNanos();
                     HostingRecord hostingRecord =
                             new HostingRecord(hostingType, hostingName, isTop);
                     ProcessRecord rec = getProcessRecordLocked(processName, info.uid);
@@ -19967,6 +19975,26 @@
 
             addStartInfoTimestampInternal(key, timestampNs, userId, uid);
         }
+
+        @Override
+        public void killApplicationSync(String pkgName, int appId, int userId,
+                String reason, int exitInfoReason) {
+            if (pkgName == null) {
+                return;
+            }
+            // Make sure the uid is valid.
+            if (appId < 0) {
+                Slog.w(TAG, "Invalid appid specified for pkg : " + pkgName);
+                return;
+            }
+            synchronized (ActivityManagerService.this) {
+                ActivityManagerService.this.forceStopPackageLocked(pkgName, appId,
+                        /* callerWillRestart= */ false, /*purgeCache= */ false,
+                        /* doit= */ true, /* evenPersistent= */ false,
+                        /* uninstalling= */ false, /* packageStateStopped= */ false,
+                        userId, reason, exitInfoReason);
+            }
+        }
     }
 
     long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 5af9424..bbd4323 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -115,6 +115,7 @@
 import android.util.ArraySet;
 import android.util.DebugUtils;
 import android.util.DisplayMetrics;
+import android.util.SparseArray;
 import android.util.TeeWriter;
 import android.util.proto.ProtoOutputStream;
 import android.view.Choreographer;
@@ -129,6 +130,7 @@
 import com.android.server.am.nano.Capability;
 import com.android.server.am.nano.FrameworkCapability;
 import com.android.server.am.nano.VMCapability;
+import com.android.server.am.nano.VMInfo;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.utils.Slogf;
@@ -275,9 +277,9 @@
                 case "compact":
                     return runCompact(pw);
                 case "freeze":
-                    return runFreeze(pw);
+                    return runFreeze(pw, true);
                 case "unfreeze":
-                    return runUnfreeze(pw);
+                    return runFreeze(pw, false);
                 case "instrument":
                     getOutPrintWriter().println("Error: must be invoked through 'am instrument'.");
                     return -1;
@@ -459,6 +461,8 @@
                 return -1;
             }
         }
+        String vmName = System.getProperty("java.vm.name", "?");
+        String vmVersion = System.getProperty("java.vm.version", "?");
 
         if (outputAsProtobuf) {
             Capabilities capabilities = new Capabilities();
@@ -485,6 +489,11 @@
                 capabilities.frameworkCapabilities[i] = cap;
             }
 
+            VMInfo vmInfo = new VMInfo();
+            vmInfo.name = vmName;
+            vmInfo.version = vmVersion;
+            capabilities.vmInfo = vmInfo;
+
             try {
                 getRawOutputStream().write(Capabilities.toByteArray(capabilities));
             } catch (IOException e) {
@@ -504,6 +513,8 @@
             for (String capability : Debug.getFeatureList()) {
                 pw.println("framework:" + capability);
             }
+            pw.println("vm_name:" + vmName);
+            pw.println("vm_version:" + vmVersion);
         }
         return 0;
     }
@@ -1203,45 +1214,27 @@
     }
 
     @NeverCompile
-    int runFreeze(PrintWriter pw) throws RemoteException {
+    int runFreeze(PrintWriter pw, boolean freeze) throws RemoteException {
         String freezerOpt = getNextOption();
         boolean isSticky = false;
-        if (freezerOpt != null) {
-            isSticky = freezerOpt.equals("--sticky");
-        }
-        ProcessRecord app = getProcessFromShell();
-        if (app == null) {
-            getErrPrintWriter().println("Error: could not find process");
-            return -1;
-        }
-        pw.println("Freezing pid: " + app.mPid + " sticky=" + isSticky);
-        synchronized (mInternal) {
-            synchronized (mInternal.mProcLock) {
-                app.mOptRecord.setFreezeSticky(isSticky);
-                mInternal.mOomAdjuster.mCachedAppOptimizer.forceFreezeAppAsyncLSP(app);
-            }
-        }
-        return 0;
-    }
 
-    @NeverCompile
-    int runUnfreeze(PrintWriter pw) throws RemoteException {
-        String freezerOpt = getNextOption();
-        boolean isSticky = false;
         if (freezerOpt != null) {
             isSticky = freezerOpt.equals("--sticky");
         }
-        ProcessRecord app = getProcessFromShell();
-        if (app == null) {
-            getErrPrintWriter().println("Error: could not find process");
+        ProcessRecord proc = getProcessFromShell();
+        if (proc == null) {
             return -1;
         }
-        pw.println("Unfreezing pid: " + app.mPid);
+        pw.print(freeze ? "Freezing" : "Unfreezing");
+        pw.print(" process " + proc.processName);
+        pw.println(" (" + proc.mPid + ") sticky=" + isSticky);
         synchronized (mInternal) {
             synchronized (mInternal.mProcLock) {
-                synchronized (mInternal.mOomAdjuster.mCachedAppOptimizer.mFreezerLock) {
-                    app.mOptRecord.setFreezeSticky(isSticky);
-                    mInternal.mOomAdjuster.mCachedAppOptimizer.unfreezeAppInternalLSP(app, 0,
+                proc.mOptRecord.setFreezeSticky(isSticky);
+                if (freeze) {
+                    mInternal.mOomAdjuster.mCachedAppOptimizer.forceFreezeAppAsyncLSP(proc);
+                } else {
+                    mInternal.mOomAdjuster.mCachedAppOptimizer.unfreezeAppInternalLSP(proc, 0,
                             true);
                 }
             }
@@ -1250,43 +1243,42 @@
     }
 
     /**
-     * Parses from the shell the process name and user id if provided and provides the corresponding
-     * {@link ProcessRecord)} If no user is provided, it will fallback to current user.
-     * Example usage: {@code <processname> --user current} or {@code <processname>}
-     * @return process record of process, null if none found.
+     * Parses from the shell the pid or process name and provides the corresponding
+     * {@link ProcessRecord}.
+     * Example usage: {@code <processname>} or {@code <pid>}
+     * @return process record of process, null if none or more than one found.
      * @throws RemoteException
      */
     @NeverCompile
     ProcessRecord getProcessFromShell() throws RemoteException {
-        ProcessRecord app;
-        String processName = getNextArgRequired();
-        synchronized (mInternal.mProcLock) {
-            // Default to current user
-            int userId = getUserIdFromShellOrFallback();
-            final int uid =
-                    mInternal.getPackageManagerInternal().getPackageUid(processName, 0, userId);
-            app = mInternal.getProcessRecordLocked(processName, uid);
+        ProcessRecord proc = null;
+        String process = getNextArgRequired();
+        try {
+            int pid = Integer.parseInt(process);
+            synchronized (mInternal.mPidsSelfLocked) {
+                proc = mInternal.mPidsSelfLocked.get(pid);
+            }
+        } catch (NumberFormatException e) {
+            // Fallback to process name if it's not a valid pid
         }
-        return app;
-    }
 
-    /**
-     * @return User id from command line provided in the form of
-     *  {@code --user <userid|current|all>} and if the argument is not found it will fallback
-     *  to current user.
-     * @throws RemoteException
-     */
-    @NeverCompile
-    int getUserIdFromShellOrFallback() throws RemoteException {
-        int userId = mInterface.getCurrentUserId();
-        String userOpt = getNextOption();
-        if (userOpt != null && "--user".equals(userOpt)) {
-            int inputUserId = UserHandle.parseUserArg(getNextArgRequired());
-            if (inputUserId != UserHandle.USER_CURRENT) {
-                userId = inputUserId;
+        if (proc == null) {
+            synchronized (mInternal.mProcLock) {
+                ArrayMap<String, SparseArray<ProcessRecord>> all =
+                        mInternal.mProcessList.getProcessNamesLOSP().getMap();
+                SparseArray<ProcessRecord> procs = all.get(process);
+                if (procs == null || procs.size() == 0) {
+                    getErrPrintWriter().println("Error: could not find process");
+                    return null;
+                } else if (procs.size() > 1) {
+                    getErrPrintWriter().println("Error: more than one processes found");
+                    return null;
+                }
+                proc = procs.valueAt(0);
             }
         }
-        return userId;
+
+        return proc;
     }
 
     int runDumpHeap(PrintWriter pw) throws RemoteException {
@@ -4306,24 +4298,26 @@
             pw.println("      --allow-background-activity-starts: The receiver may start activities");
             pw.println("          even if in the background.");
             pw.println("      --async: Send without waiting for the completion of the receiver.");
-            pw.println("  compact [some|full] <process_name> [--user <USER_ID>]");
-            pw.println("      Perform a single process compaction.");
+            pw.println("  compact {some|full} <PROCESS>");
+            pw.println("      Perform a single process compaction. The given <PROCESS> argument");
+            pw.println("          may be either a process name or pid.");
             pw.println("      some: execute file compaction.");
             pw.println("      full: execute anon + file compaction.");
-            pw.println("      system: system compaction.");
             pw.println("  compact system");
             pw.println("      Perform a full system compaction.");
-            pw.println("  compact native [some|full] <pid>");
+            pw.println("  compact native {some|full} <pid>");
             pw.println("      Perform a native compaction for process with <pid>.");
             pw.println("      some: execute file compaction.");
             pw.println("      full: execute anon + file compaction.");
-            pw.println("  freeze [--sticky] <processname> [--user <USER_ID>]");
-            pw.println("      Freeze a process.");
-            pw.println("        --sticky: persists the frozen state for the process lifetime or");
+            pw.println("  freeze [--sticky] <PROCESS>");
+            pw.println("      Freeze a process. The given <PROCESS> argument");
+            pw.println("          may be either a process name or pid.  Options are:");
+            pw.println("      --sticky: persists the frozen state for the process lifetime or");
             pw.println("                  until an unfreeze is triggered via shell");
-            pw.println("  unfreeze [--sticky] <processname> [--user <USER_ID>]");
-            pw.println("      Unfreeze a process.");
-            pw.println("        --sticky: persists the unfrozen state for the process lifetime or");
+            pw.println("  unfreeze [--sticky] <PROCESS>");
+            pw.println("      Unfreeze a process. The given <PROCESS> argument");
+            pw.println("          may be either a process name or pid.  Options are:");
+            pw.println("      --sticky: persists the unfrozen state for the process lifetime or");
             pw.println("                  until a freeze is triggered via shell");
             pw.println("  instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]");
             pw.println("          [--user <USER_ID> | current]");
@@ -4552,10 +4546,9 @@
             pw.println("           1: crop_windows");
             pw.println("           2: resizeable");
             pw.println("           3: resizeable_and_pipable");
-            pw.println("       resize <TASK_ID> <LEFT,TOP,RIGHT,BOTTOM>");
-            pw.println("           Makes sure <TASK_ID> is in a stack with the specified bounds.");
-            pw.println("           Forces the task to be resizeable and creates a stack if no existing stack");
-            pw.println("           has the specified bounds.");
+            pw.println("       resize <TASK_ID> <LEFT> <TOP> <RIGHT> <BOTTOM>");
+            pw.println("           The task is resized only if it is in multi-window windowing");
+            pw.println("           mode or freeform windowing mode.");
             pw.println("  update-appinfo <USER_ID> <PACKAGE_NAME> [<PACKAGE_NAME>...]");
             pw.println("      Update the ApplicationInfo objects of the listed packages for <USER_ID>");
             pw.println("      without restarting any processes.");
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 2cdf596..79a8518 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -294,9 +294,11 @@
                 mInProgRecords.removeAt(index);
                 return;
             }
-            info.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN);
             info.setLaunchMode(launchMode);
-            checkCompletenessAndCallback(info);
+            if (!android.app.Flags.appStartInfoTimestamps()) {
+                info.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN);
+                checkCompletenessAndCallback(info);
+            }
         }
     }
 
@@ -424,14 +426,6 @@
         }
     }
 
-    void reportApplicationOnCreateTimeNanos(ProcessRecord app, long timeNs) {
-        if (!mEnabled) {
-            return;
-        }
-        addTimestampToStart(app, timeNs,
-                ApplicationStartInfo.START_TIMESTAMP_APPLICATION_ONCREATE);
-    }
-
     /**
      * Helper functions for monitoring shell command.
      * > adb shell am start-info-detailed-monitoring [package-name]
@@ -466,41 +460,14 @@
         }
     }
 
-    /** Report a bind application timestamp to add to {@link ApplicationStartInfo}. */
-    public void reportBindApplicationTimeNanos(ProcessRecord app, long timeNs) {
-        addTimestampToStart(app, timeNs,
-                ApplicationStartInfo.START_TIMESTAMP_BIND_APPLICATION);
-    }
-
-    void reportFirstFrameTimeNanos(ProcessRecord app, long timeNs) {
-        if (!mEnabled) {
-            return;
-        }
-        addTimestampToStart(app, timeNs,
-                ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME);
-    }
-
-    void reportFullyDrawnTimeNanos(ProcessRecord app, long timeNs) {
-        if (!mEnabled) {
-            return;
-        }
-        addTimestampToStart(app, timeNs,
-                ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN);
-    }
-
-    void reportFullyDrawnTimeNanos(String processName, int uid, long timeNs) {
-        if (!mEnabled) {
-            return;
-        }
-        addTimestampToStart(processName, uid, timeNs,
-                ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN);
-    }
-
-    private void addTimestampToStart(ProcessRecord app, long timeNs, int key) {
+    void addTimestampToStart(ProcessRecord app, long timeNs, int key) {
         addTimestampToStart(app.info.packageName, app.uid, timeNs, key);
     }
 
     void addTimestampToStart(String packageName, int uid, long timeNs, int key) {
+        if (!mEnabled) {
+            return;
+        }
         synchronized (mLock) {
             AppStartInfoContainer container = mData.get(packageName, uid);
             if (container == null) {
@@ -539,9 +506,9 @@
     }
 
     /**
-     * Called whenever data is added to a {@link ApplicationStartInfo} object. Checks for
-     * completeness and triggers callback if a callback has been registered and the object
-     * is complete.
+     * Called whenever a potentially final piece of data is added to a {@link ApplicationStartInfo}
+     * object. Checks for completeness and triggers callback if a callback has been registered and
+     * the object is complete.
      */
     private void checkCompletenessAndCallback(ApplicationStartInfo startInfo) {
         synchronized (mLock) {
@@ -1124,10 +1091,23 @@
                     Long.compare(getStartTimestamp(b), getStartTimestamp(a)));
         }
 
+        /**
+         * Add the provided key/timestamp to the most recent start record, if it is currently
+         * accepting new timestamps.
+         *
+         * Will also update the start records startup state and trigger the completion listener when
+         * appropriate.
+         */
         @GuardedBy("mLock")
         void addTimestampToStartLocked(int key, long timestampNs) {
+            if (mInfos.isEmpty()) {
+                if (DEBUG) Slog.d(TAG, "No records to add to.");
+                return;
+            }
+
             // Records are sorted newest to oldest, grab record at index 0.
-            int startupState = mInfos.get(0).getStartupState();
+            ApplicationStartInfo startInfo = mInfos.get(0);
+            int startupState = startInfo.getStartupState();
 
             // If startup state is error then don't accept any further timestamps.
             if (startupState == ApplicationStartInfo.STARTUP_STATE_ERROR) {
@@ -1145,7 +1125,13 @@
                 return;
             }
 
-            mInfos.get(0).addStartupTimestamp(key, timestampNs);
+            startInfo.addStartupTimestamp(key, timestampNs);
+
+            if (key == ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME
+                    && android.app.Flags.appStartInfoTimestamps()) {
+                startInfo.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN);
+                checkCompletenessAndCallback(startInfo);
+            }
         }
 
         @GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 4f84149..58732fd 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -159,6 +159,8 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * All information we are collecting about things that can happen that impact
@@ -409,26 +411,14 @@
                 com.android.internal.R.bool.config_batteryStatsResetOnUnplugHighBatteryLevel);
         final boolean resetOnUnplugAfterSignificantCharge = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_batteryStatsResetOnUnplugAfterSignificantCharge);
-        final long powerStatsThrottlePeriodCpu = context.getResources().getInteger(
-                com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodCpu);
-        final long powerStatsThrottlePeriodMobileRadio = context.getResources().getInteger(
-                com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodMobileRadio);
-        final long powerStatsThrottlePeriodWifi = context.getResources().getInteger(
-                com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodWifi);
-        mBatteryStatsConfig =
+        BatteryStatsImpl.BatteryStatsConfig.Builder batteryStatsConfigBuilder =
                 new BatteryStatsImpl.BatteryStatsConfig.Builder()
                         .setResetOnUnplugHighBatteryLevel(resetOnUnplugHighBatteryLevel)
-                        .setResetOnUnplugAfterSignificantCharge(resetOnUnplugAfterSignificantCharge)
-                        .setPowerStatsThrottlePeriodMillis(
-                                BatteryConsumer.POWER_COMPONENT_CPU,
-                                powerStatsThrottlePeriodCpu)
-                        .setPowerStatsThrottlePeriodMillis(
-                                BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
-                                powerStatsThrottlePeriodMobileRadio)
-                        .setPowerStatsThrottlePeriodMillis(
-                                BatteryConsumer.POWER_COMPONENT_WIFI,
-                                powerStatsThrottlePeriodWifi)
-                        .build();
+                        .setResetOnUnplugAfterSignificantCharge(
+                                resetOnUnplugAfterSignificantCharge);
+        setPowerStatsThrottlePeriods(batteryStatsConfigBuilder, context.getResources().getString(
+                com.android.internal.R.string.config_powerStatsThrottlePeriods));
+        mBatteryStatsConfig = batteryStatsConfigBuilder.build();
         mPowerStatsUidResolver = new PowerStatsUidResolver();
         mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
                 systemDir, mHandler, this, this, mUserManagerUserInfoProvider, mPowerProfile,
@@ -515,6 +505,26 @@
         return config;
     }
 
+    private void setPowerStatsThrottlePeriods(BatteryStatsImpl.BatteryStatsConfig.Builder builder,
+            String configString) {
+        Matcher matcher = Pattern.compile("([^:]+):(\\d+)\\s*").matcher(configString);
+        while (matcher.find()) {
+            String powerComponentName = matcher.group(1);
+            long throttlePeriod;
+            try {
+                throttlePeriod = Long.parseLong(matcher.group(2));
+            } catch (NumberFormatException nfe) {
+                throw new IllegalArgumentException(
+                        "Invalid config_powerStatsThrottlePeriods format: " + configString);
+            }
+            if (powerComponentName.equals("*")) {
+                builder.setDefaultPowerStatsThrottlePeriodMillis(throttlePeriod);
+            } else {
+                builder.setPowerStatsThrottlePeriodMillis(powerComponentName, throttlePeriod);
+            }
+        }
+    }
+
     /**
      * Creates an instance of BatteryStatsService and restores data from stored state.
      */
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 4425a38..1379c9b 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -1021,6 +1021,7 @@
         final boolean allowWhileBooting = (r.intent.getFlags()
                 & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0;
 
+        long startTimeNs = SystemClock.uptimeNanos();
         if (DEBUG_BROADCAST) logv("Scheduling " + r + " to cold " + queue);
         queue.app = mService.startProcessLocked(queue.processName, info, true, intentFlags,
                 hostingRecord, zygotePolicyFlags, allowWhileBooting, false);
@@ -1032,8 +1033,7 @@
         }
         // TODO: b/335420031 - cache receiver intent to avoid multiple calls to getReceiverIntent.
         mService.mProcessList.getAppStartInfoTracker().handleProcessBroadcastStart(
-                SystemClock.elapsedRealtimeNanos(), queue.app, r.getReceiverIntent(receiver),
-                r.alarm /* isAlarm */);
+                startTimeNs, queue.app, r.getReceiverIntent(receiver), r.alarm /* isAlarm */);
         return false;
     }
 
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
index f2b9b25..31704c4 100644
--- a/services/core/java/com/android/server/am/ConnectionRecord.java
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -136,6 +136,13 @@
                 false, oomAdjReason, UNKNOWN_ADJ, false, false);
     }
 
+    @Override
+    public boolean canAffectCapabilities() {
+        return hasFlag(Context.BIND_INCLUDE_CAPABILITIES
+                | Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS);
+    }
+
+
     public long getFlags() {
         return flags;
     }
diff --git a/services/core/java/com/android/server/am/ContentProviderConnection.java b/services/core/java/com/android/server/am/ContentProviderConnection.java
index 3988277..ae5ae01 100644
--- a/services/core/java/com/android/server/am/ContentProviderConnection.java
+++ b/services/core/java/com/android/server/am/ContentProviderConnection.java
@@ -82,6 +82,12 @@
                 false, oomAdjReason, UNKNOWN_ADJ, false, false);
     }
 
+    @Override
+    public boolean canAffectCapabilities() {
+        return false;
+    }
+
+
     public void startAssociationIfNeeded() {
         // If we don't already have an active association, create one...  but only if this
         // is an association between two different processes.
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 28fd197..a8b9e43 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -179,7 +179,7 @@
         final int expectedUserId = userId;
         synchronized (mService) {
             long startTime = SystemClock.uptimeMillis();
-            long startElapsedTimeNs = SystemClock.elapsedRealtimeNanos();
+            long startTimeNs = SystemClock.uptimeNanos();
 
             ProcessRecord r = null;
             if (caller != null) {
@@ -587,7 +587,7 @@
                                     firstLaunch,
                                     0L /* TODO: stoppedDuration */);
                             mService.mProcessList.getAppStartInfoTracker()
-                                    .handleProcessContentProviderStart(startElapsedTimeNs, proc);
+                                    .handleProcessContentProviderStart(startTimeNs, proc);
                         }
                         cpr.launchingApp = proc;
                         mLaunchingProviders.add(cpr);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index a289dd1..8647750 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2610,6 +2610,15 @@
                 }
             }
 
+            // Sandbox should be able to control audio only when bound client
+            // has this capability.
+            if ((cstate.getCurCapability()
+                    & PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL) != 0) {
+                if (app.isSdkSandbox) {
+                    capability |= PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL;
+                }
+            }
+
             if (couldRecurse && shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) {
                 return false;
             }
@@ -3268,7 +3277,13 @@
         }
 
         final int curSchedGroup = state.getCurrentSchedulingGroup();
-        if (state.getSetSchedGroup() != curSchedGroup) {
+        if (app.getWaitingToKill() != null && app.mReceivers.numberOfCurReceivers() == 0
+                && ActivityManager.isProcStateBackground(state.getCurProcState())
+                && !state.hasStartedServices()) {
+            app.killLocked(app.getWaitingToKill(), ApplicationExitInfo.REASON_USER_REQUESTED,
+                    ApplicationExitInfo.SUBREASON_REMOVE_TASK, true);
+            success = false;
+        } else if (state.getSetSchedGroup() != curSchedGroup) {
             int oldSchedGroup = state.getSetSchedGroup();
             state.setSetSchedGroup(curSchedGroup);
             if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) {
@@ -3276,74 +3291,66 @@
                         + " to " + curSchedGroup + ": " + state.getAdjType();
                 reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
             }
-            if (app.getWaitingToKill() != null && app.mReceivers.numberOfCurReceivers() == 0
-                    && ActivityManager.isProcStateBackground(state.getSetProcState())
-                    && !state.hasStartedServices()) {
-                app.killLocked(app.getWaitingToKill(), ApplicationExitInfo.REASON_USER_REQUESTED,
-                        ApplicationExitInfo.SUBREASON_REMOVE_TASK, true);
-                success = false;
-            } else {
-                int processGroup;
-                switch (curSchedGroup) {
-                    case SCHED_GROUP_BACKGROUND:
-                        processGroup = THREAD_GROUP_BACKGROUND;
-                        break;
-                    case SCHED_GROUP_TOP_APP:
-                    case SCHED_GROUP_TOP_APP_BOUND:
-                        processGroup = THREAD_GROUP_TOP_APP;
-                        break;
-                    case SCHED_GROUP_RESTRICTED:
-                        processGroup = THREAD_GROUP_RESTRICTED;
-                        break;
-                    default:
-                        processGroup = THREAD_GROUP_DEFAULT;
-                        break;
-                }
-                mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage(
-                        0 /* unused */, app.getPid(), processGroup, app.processName));
-                try {
-                    final int renderThreadTid = app.getRenderThreadTid();
-                    if (curSchedGroup == SCHED_GROUP_TOP_APP) {
-                        // do nothing if we already switched to RT
-                        if (oldSchedGroup != SCHED_GROUP_TOP_APP) {
-                            app.getWindowProcessController().onTopProcChanged();
-                            if (app.useFifoUiScheduling()) {
-                                // Switch UI pipeline for app to SCHED_FIFO
-                                state.setSavedPriority(Process.getThreadPriority(app.getPid()));
-                                ActivityManagerService.setFifoPriority(app, true /* enable */);
-                            } else {
-                                // Boost priority for top app UI and render threads
-                                setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
-                                if (renderThreadTid != 0) {
-                                    try {
-                                        setThreadPriority(renderThreadTid,
-                                                THREAD_PRIORITY_TOP_APP_BOOST);
-                                    } catch (IllegalArgumentException e) {
-                                        // thread died, ignore
-                                    }
+            int processGroup;
+            switch (curSchedGroup) {
+                case SCHED_GROUP_BACKGROUND:
+                    processGroup = THREAD_GROUP_BACKGROUND;
+                    break;
+                case SCHED_GROUP_TOP_APP:
+                case SCHED_GROUP_TOP_APP_BOUND:
+                    processGroup = THREAD_GROUP_TOP_APP;
+                    break;
+                case SCHED_GROUP_RESTRICTED:
+                    processGroup = THREAD_GROUP_RESTRICTED;
+                    break;
+                default:
+                    processGroup = THREAD_GROUP_DEFAULT;
+                    break;
+            }
+            mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage(
+                    0 /* unused */, app.getPid(), processGroup, app.processName));
+            try {
+                final int renderThreadTid = app.getRenderThreadTid();
+                if (curSchedGroup == SCHED_GROUP_TOP_APP) {
+                    // do nothing if we already switched to RT
+                    if (oldSchedGroup != SCHED_GROUP_TOP_APP) {
+                        app.getWindowProcessController().onTopProcChanged();
+                        if (app.useFifoUiScheduling()) {
+                            // Switch UI pipeline for app to SCHED_FIFO
+                            state.setSavedPriority(Process.getThreadPriority(app.getPid()));
+                            ActivityManagerService.setFifoPriority(app, true /* enable */);
+                        } else {
+                            // Boost priority for top app UI and render threads
+                            setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
+                            if (renderThreadTid != 0) {
+                                try {
+                                    setThreadPriority(renderThreadTid,
+                                            THREAD_PRIORITY_TOP_APP_BOOST);
+                                } catch (IllegalArgumentException e) {
+                                    // thread died, ignore
                                 }
                             }
                         }
-                    } else if (oldSchedGroup == SCHED_GROUP_TOP_APP
-                            && curSchedGroup != SCHED_GROUP_TOP_APP) {
-                        app.getWindowProcessController().onTopProcChanged();
-                        if (app.useFifoUiScheduling()) {
-                            // Reset UI pipeline to SCHED_OTHER
-                            ActivityManagerService.setFifoPriority(app, false /* enable */);
-                            setThreadPriority(app.getPid(), state.getSavedPriority());
-                        } else {
-                            // Reset priority for top app UI and render threads
-                            setThreadPriority(app.getPid(), 0);
-                        }
+                    }
+                } else if (oldSchedGroup == SCHED_GROUP_TOP_APP
+                        && curSchedGroup != SCHED_GROUP_TOP_APP) {
+                    app.getWindowProcessController().onTopProcChanged();
+                    if (app.useFifoUiScheduling()) {
+                        // Reset UI pipeline to SCHED_OTHER
+                        ActivityManagerService.setFifoPriority(app, false /* enable */);
+                        setThreadPriority(app.getPid(), state.getSavedPriority());
+                    } else {
+                        // Reset priority for top app UI and render threads
+                        setThreadPriority(app.getPid(), 0);
+                    }
 
-                        if (renderThreadTid != 0) {
-                            setThreadPriority(renderThreadTid, THREAD_PRIORITY_DISPLAY);
-                        }
+                    if (renderThreadTid != 0) {
+                        setThreadPriority(renderThreadTid, THREAD_PRIORITY_DISPLAY);
                     }
-                } catch (Exception e) {
-                    if (DEBUG_ALL) {
-                        Slog.w(TAG, "Failed setting thread priority of " + app.getPid(), e);
-                    }
+                }
+            } catch (Exception e) {
+                if (DEBUG_ALL) {
+                    Slog.w(TAG, "Failed setting thread priority of " + app.getPid(), e);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 3268b66..9600317 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManager.PROCESS_CAPABILITY_BFSL;
 import static android.app.ActivityManager.PROCESS_STATE_BACKUP;
 import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
@@ -521,6 +522,11 @@
          */
         void computeHostOomAdjLSP(OomAdjuster oomAdjuster, ProcessRecord host, ProcessRecord client,
                 long now, ProcessRecord topApp, boolean doingAll, int oomAdjReason, int cachedAdj);
+
+        /**
+         * Returns true if this connection can propagate capabilities.
+         */
+        boolean canAffectCapabilities();
     }
 
     /**
@@ -553,16 +559,29 @@
      */
     private class ComputeConnectionIgnoringReachableClientsConsumer implements
             BiConsumer<Connection, ProcessRecord> {
-        public OomAdjusterArgs args = null;
+        private OomAdjusterArgs mArgs = null;
+        public boolean hasReachableClient = false;
+
+        public void init(OomAdjusterArgs args) {
+            mArgs = args;
+            hasReachableClient = false;
+        }
 
         @Override
         public void accept(Connection conn, ProcessRecord client) {
-            final ProcessRecord host = args.mApp;
-            final ProcessRecord topApp = args.mTopApp;
-            final long now = args.mNow;
-            final @OomAdjReason int oomAdjReason = args.mOomAdjReason;
+            final ProcessRecord host = mArgs.mApp;
+            final ProcessRecord topApp = mArgs.mTopApp;
+            final long now = mArgs.mNow;
+            final @OomAdjReason int oomAdjReason = mArgs.mOomAdjReason;
 
-            if (client.mState.isReachable()) return;
+            if (client.mState.isReachable()) {
+                hasReachableClient = true;
+                return;
+            }
+
+            if (unimportantConnectionLSP(conn, host, client)) {
+                return;
+            }
 
             conn.computeHostOomAdjLSP(OomAdjusterModernImpl.this, host, client, now, topApp, false,
                     oomAdjReason, UNKNOWN_ADJ);
@@ -591,6 +610,10 @@
             final int prevProcState = host.mState.getCurProcState();
             final int prevAdj = host.mState.getCurRawAdj();
 
+            if (unimportantConnectionLSP(conn, host, client)) {
+                return;
+            }
+
             conn.computeHostOomAdjLSP(OomAdjusterModernImpl.this, host, client, now, topApp,
                     fullUpdate, oomAdjReason, cachedAdj);
 
@@ -874,7 +897,7 @@
         // processes cannot change as a part of this update, their current values can be used
         // right now.
         mProcessRecordProcStateNodes.resetLastNodes();
-        initReachableStatesLSP(reachables, mTmpOomAdjusterArgs);
+        initReachableStatesLSP(reachables, targets.size(), mTmpOomAdjusterArgs);
 
         // Set adj last nodes now, this way a process will only be reevaluated during the adj node
         // iteration if they adj score changed during the procState node iteration.
@@ -914,8 +937,6 @@
     /**
      * Mark all processes reachable from the {@code reachables} processes and add them to the
      * provided {@code reachables} list (targets excluded).
-     *
-     * Returns true if a cycle exists within the reachable process graph.
      */
     @GuardedBy({"mService", "mProcLock"})
     private void collectAndMarkReachableProcessesLSP(ArrayList<ProcessRecord> reachables) {
@@ -930,8 +951,35 @@
      * Calculate initial importance states for {@code reachables} and update their slot position
      * if necessary.
      */
-    private void initReachableStatesLSP(ArrayList<ProcessRecord> reachables, OomAdjusterArgs args) {
-        for (int i = 0, size = reachables.size(); i < size; i++) {
+    private void initReachableStatesLSP(ArrayList<ProcessRecord> reachables, int targetCount,
+            OomAdjusterArgs args) {
+        int i = 0;
+        boolean initReachables = !Flags.skipUnimportantConnections();
+        for (; i < targetCount && !initReachables; i++) {
+            final ProcessRecord target = reachables.get(i);
+            final int prevProcState = target.mState.getCurProcState();
+            final int prevAdj = target.mState.getCurRawAdj();
+            final int prevCapability = target.mState.getCurCapability();
+            final boolean prevShouldNotFreeze = target.mOptRecord.shouldNotFreeze();
+
+            args.mApp = target;
+            // If target client is a reachable, reachables need to be reinited in case this
+            // client is important enough to change this target in the computeConnection step.
+            initReachables |= computeOomAdjIgnoringReachablesLSP(args);
+            // If target lowered in importance, reachables need to be reinited because this
+            // target may have been the source of a reachable's current importance.
+            initReachables |= selfImportanceLoweredLSP(target, prevProcState, prevAdj,
+                    prevCapability, prevShouldNotFreeze);
+
+            updateProcStateSlot(target, prevProcState);
+            updateAdjSlot(target, prevAdj);
+        }
+
+        if (!initReachables) {
+            return;
+        }
+
+        for (int size = reachables.size(); i < size; i++) {
             final ProcessRecord reachable = reachables.get(i);
             final int prevProcState = reachable.mState.getCurProcState();
             final int prevAdj = reachable.mState.getCurRawAdj();
@@ -948,9 +996,11 @@
      * Calculate initial importance states for {@code app}.
      * Processes not marked reachable cannot change as a part of this update, so connections from
      * those process can be calculated now.
+     *
+     * Returns true if any client connection was skipped due to a reachablity cycle.
      */
     @GuardedBy({"mService", "mProcLock"})
-    private void computeOomAdjIgnoringReachablesLSP(OomAdjusterArgs args) {
+    private boolean computeOomAdjIgnoringReachablesLSP(OomAdjusterArgs args) {
         final ProcessRecord app = args.mApp;
         final ProcessRecord topApp = args.mTopApp;
         final long now = args.mNow;
@@ -958,8 +1008,9 @@
 
         computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, false, now, false, false, oomAdjReason, false);
 
-        mComputeConnectionIgnoringReachableClientsConsumer.args = args;
+        mComputeConnectionIgnoringReachableClientsConsumer.init(args);
         forEachClientConnectionLSP(app, mComputeConnectionIgnoringReachableClientsConsumer);
+        return mComputeConnectionIgnoringReachableClientsConsumer.hasReachableClient;
     }
 
     /**
@@ -1039,6 +1090,7 @@
                     } else {
                         client = cr.binding.client;
                     }
+                    if (client == null || client == app) continue;
                     connectionConsumer.accept(cr, client);
                 }
             }
@@ -1053,4 +1105,66 @@
             }
         }
     }
+
+    /**
+     * Returns true if at least one the provided values is more important than those in {@code app}.
+     */
+    @GuardedBy({"mService", "mProcLock"})
+    private static boolean selfImportanceLoweredLSP(ProcessRecord app, int prevProcState,
+            int prevAdj, int prevCapability, boolean prevShouldNotFreeze) {
+        if (app.mState.getCurProcState() > prevProcState) {
+            return true;
+        }
+        if (app.mState.getCurRawAdj() > prevAdj)  {
+            return true;
+        }
+        if ((app.mState.getCurCapability() & prevCapability) != prevCapability)  {
+            return true;
+        }
+        if (!app.mOptRecord.shouldNotFreeze() && prevShouldNotFreeze) {
+            // No long marked as should not freeze.
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns whether a host connection evaluation can be skipped due to lack of importance.
+     * Note: the client and host need to be provided as well for the isolated and sandbox
+     * scenarios.
+     */
+    @GuardedBy({"mService", "mProcLock"})
+    private static boolean unimportantConnectionLSP(Connection conn,
+            ProcessRecord host, ProcessRecord client) {
+        if (!Flags.skipUnimportantConnections()) {
+            // Feature not enabled, just return false so the connection is evaluated.
+            return false;
+        }
+        if (host.mState.getCurProcState() > client.mState.getCurProcState()) {
+            return false;
+        }
+        if (host.mState.getCurRawAdj() > client.mState.getCurRawAdj())  {
+            return false;
+        }
+        final int serviceCapability = host.mState.getCurCapability();
+        final int clientCapability = client.mState.getCurCapability();
+        if ((serviceCapability & clientCapability) != clientCapability) {
+            // Client has a capability the host does not have.
+            if ((clientCapability & PROCESS_CAPABILITY_BFSL) == PROCESS_CAPABILITY_BFSL
+                    && (serviceCapability & PROCESS_CAPABILITY_BFSL) == 0) {
+                // The BFSL capability does not need a flag to propagate.
+                return false;
+            }
+            if (conn.canAffectCapabilities()) {
+                // One of these bind flags may propagate that capability.
+                return false;
+            }
+        }
+
+        if (!host.mOptRecord.shouldNotFreeze() && client.mOptRecord.shouldNotFreeze()) {
+            // If the client is marked as should not freeze, so should the host.
+            return false;
+        }
+        return true;
+    }
 }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 6779f7a..a5449a0 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -37,6 +37,7 @@
 import static android.system.OsConstants.EAGAIN;
 
 import static com.android.sdksandbox.flags.Flags.selinuxSdkSandboxAudit;
+import static com.android.sdksandbox.flags.Flags.selinuxSdkSandboxInputSelector;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
@@ -2065,11 +2066,15 @@
             }
         }
 
-        return app.info.seInfo
-                + (TextUtils.isEmpty(app.info.seInfoUser) ? "" : app.info.seInfoUser) + extraInfo;
+        if (selinuxSdkSandboxInputSelector()) {
+            return app.info.seInfo + extraInfo + TextUtils.emptyIfNull(app.info.seInfoUser);
+        } else {
+            return app.info.seInfo
+                    + (TextUtils.isEmpty(app.info.seInfoUser) ? "" : app.info.seInfoUser)
+                    + extraInfo;
+        }
     }
 
-
     @GuardedBy("mService")
     boolean startProcessLocked(HostingRecord hostingRecord, String entryPoint, ProcessRecord app,
             int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags, int mountExternal,
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 8eca4fc..2184340 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -690,10 +690,14 @@
         private long mTimeLimitExceededAt = Long.MIN_VALUE;
         @UptimeMillisLong
         private long mTotalRuntime = 0;
+        private int mNumParallelServices = 0;
 
-        TimeLimitedFgsInfo(@UptimeMillisLong long startTime) {
-            mFirstFgsStartUptime = startTime;
-            mFirstFgsStartRealtime = SystemClock.elapsedRealtime();
+        public void noteFgsFgsStart(@UptimeMillisLong long startTime) {
+            mNumParallelServices++;
+            if (mNumParallelServices == 1) {
+                mFirstFgsStartUptime = startTime;
+                mFirstFgsStartRealtime = SystemClock.elapsedRealtime();
+            }
             mLastFgsStartTime = startTime;
         }
 
@@ -707,17 +711,23 @@
             return mFirstFgsStartRealtime;
         }
 
-        public void setLastFgsStartTime(@UptimeMillisLong long startTime) {
-            mLastFgsStartTime = startTime;
-        }
-
         @UptimeMillisLong
         public long getLastFgsStartTime() {
             return mLastFgsStartTime;
         }
 
-        public void updateTotalRuntime() {
-            mTotalRuntime += SystemClock.uptimeMillis() - mLastFgsStartTime;
+        public void decNumParallelServices() {
+            if (mNumParallelServices > 0) {
+                mNumParallelServices--;
+            }
+            if (mNumParallelServices == 0) {
+                mLastFgsStartTime = 0;
+            }
+        }
+
+        public void updateTotalRuntime(@UptimeMillisLong long nowUptime) {
+            mTotalRuntime += nowUptime - mLastFgsStartTime;
+            mLastFgsStartTime = nowUptime;
         }
 
         @UptimeMillisLong
@@ -735,6 +745,7 @@
         }
 
         public void reset() {
+            mNumParallelServices = 0;
             mFirstFgsStartUptime = 0;
             mFirstFgsStartRealtime = 0;
             mLastFgsStartTime = 0;
@@ -1872,8 +1883,8 @@
     /**
      * Called when a time-limited FGS starts.
      */
-    public TimeLimitedFgsInfo createTimeLimitedFgsInfo(@UptimeMillisLong long nowUptime) {
-        return new TimeLimitedFgsInfo(nowUptime);
+    public TimeLimitedFgsInfo createTimeLimitedFgsInfo() {
+        return new TimeLimitedFgsInfo();
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 9b83ede..9bf5c21 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -20,6 +20,8 @@
 import android.content.ContentResolver;
 import android.database.ContentObserver;
 import android.net.Uri;
+import android.net.LocalSocketAddress;
+import android.net.LocalSocket;
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.SystemProperties;
@@ -27,9 +29,19 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Slog;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import android.aconfigd.Aconfigd.StorageRequestMessage;
+import android.aconfigd.Aconfigd.StorageRequestMessages;
+import android.aconfigd.Aconfigd.StorageReturnMessage;
+import android.aconfigd.Aconfigd.StorageReturnMessages;
+import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileReader;
@@ -128,6 +140,7 @@
         "aoc",
         "app_widgets",
         "arc_next",
+        "art_mainline",
         "avic",
         "biometrics",
         "biometrics_framework",
@@ -144,6 +157,7 @@
         "car_telemetry",
         "codec_fwk",
         "companion",
+        "com_android_adbd",
         "content_protection",
         "context_hub",
         "core_experiments_team_internal",
@@ -184,6 +198,7 @@
         "pmw",
         "power",
         "preload_safety",
+        "printing",
         "privacy_infra_policy",
         "resource_manager",
         "responsible_apis",
@@ -223,6 +238,8 @@
     public static final String NAMESPACE_REBOOT_STAGING = "staged";
     public static final String NAMESPACE_REBOOT_STAGING_DELIMITER = "*";
 
+    public static final String NAMESPACE_LOCAL_OVERRIDES = "device_config_overrides";
+
     private final String[] mGlobalSettings;
 
     private final String[] mDeviceConfigScopes;
@@ -248,11 +265,11 @@
             Uri settingUri = Settings.Global.getUriFor(globalSetting);
             String propName = makePropertyName(GLOBAL_SETTINGS_CATEGORY, globalSetting);
             if (settingUri == null) {
-                log("setting uri is null for globalSetting " + globalSetting);
+                logErr("setting uri is null for globalSetting " + globalSetting);
                 continue;
             }
             if (propName == null) {
-                log("invalid prop name for globalSetting " + globalSetting);
+                logErr("invalid prop name for globalSetting " + globalSetting);
                 continue;
             }
 
@@ -280,7 +297,7 @@
                         for (String key : properties.getKeyset()) {
                             String propertyName = makePropertyName(scope, key);
                             if (propertyName == null) {
-                                log("unable to construct system property for " + scope + "/"
+                                logErr("unable to construct system property for " + scope + "/"
                                         + key);
                                 return;
                             }
@@ -292,7 +309,7 @@
                             // sys prop slot can be removed.
                             String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key);
                             if (aconfigPropertyName == null) {
-                                log("unable to construct system property for " + scope + "/"
+                                logErr("unable to construct system property for " + scope + "/"
                                         + key);
                                 return;
                             }
@@ -310,7 +327,7 @@
                         for (String key : properties.getKeyset()) {
                             String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key);
                             if (aconfigPropertyName == null) {
-                                log("unable to construct system property for " + scope + "/"
+                                logErr("unable to construct system property for " + scope + "/"
                                         + key);
                                 return;
                             }
@@ -328,6 +345,7 @@
               HashMap<String, HashMap<String, String>> propsToStage =
                   getStagedFlagsWithValueChange(properties);
 
+              // send prop stage request to sys prop
               for (HashMap.Entry<String, HashMap<String, String>> entry : propsToStage.entrySet()) {
                 String actualNamespace = entry.getKey();
                 HashMap<String, String> flagValuesToStage = entry.getValue();
@@ -339,7 +357,7 @@
 
                   if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
                       || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
-                    log("unable to construct system property for " + actualNamespace
+                    logErr("unable to construct system property for " + actualNamespace
                         + "/" + flagName);
                     continue;
                   }
@@ -348,7 +366,171 @@
                 }
               }
 
-            });
+              // send prop stage request to new storage
+              if (enableAconfigStorageDaemon()) {
+                  stageFlagsInNewStorage(propsToStage);
+              }
+
+        });
+
+        // add prop sync callback for flag local overrides
+        DeviceConfig.addOnPropertiesChangedListener(
+            NAMESPACE_LOCAL_OVERRIDES,
+            AsyncTask.THREAD_POOL_EXECUTOR,
+            (DeviceConfig.Properties properties) -> {
+                if (enableAconfigStorageDaemon()) {
+                    setLocalOverridesInNewStorage(properties);
+                }
+        });
+    }
+
+    /**
+     * apply flag local override in aconfig new storage
+     * @param requests: request proto output stream
+     * @return aconfigd socket return as proto input stream
+     */
+    static ProtoInputStream sendAconfigdRequests(ProtoOutputStream requests) {
+        // connect to aconfigd socket
+        LocalSocket client = new LocalSocket();
+        try{
+            client.connect(new LocalSocketAddress(
+                "aconfigd", LocalSocketAddress.Namespace.RESERVED));
+            Slog.d(TAG, "connected to aconfigd socket");
+        } catch (IOException ioe) {
+            logErr("failed to connect to aconfigd socket", ioe);
+            return null;
+        }
+
+        DataInputStream inputStream = null;
+        DataOutputStream outputStream = null;
+        try {
+            inputStream = new DataInputStream(client.getInputStream());
+            outputStream = new DataOutputStream(client.getOutputStream());
+        } catch (IOException ioe) {
+            logErr("failed to get local socket iostreams", ioe);
+            return null;
+        }
+
+        // send requests
+        try {
+            byte[] requests_bytes = requests.getBytes();
+            outputStream.writeInt(requests_bytes.length);
+            outputStream.write(requests_bytes, 0, requests_bytes.length);
+            Slog.d(TAG, "flag override requests sent to aconfigd");
+        } catch (IOException ioe) {
+            logErr("failed to send requests to aconfigd", ioe);
+            return null;
+        }
+
+        // read return
+        try {
+            int num_bytes = inputStream.readInt();
+            ProtoInputStream returns = new ProtoInputStream(inputStream);
+            Slog.d(TAG, "received " + num_bytes + " bytes back from aconfigd");
+            return returns;
+        } catch (IOException ioe) {
+            logErr("failed to read requests return from aconfigd", ioe);
+            return null;
+        }
+    }
+
+    /**
+     * serialize a flag override request
+     * @param proto
+     */
+    static void writeFlagOverrideRequest(
+        ProtoOutputStream proto, String packageName, String flagName, String flagValue,
+        boolean isLocal) {
+      long msgsToken = proto.start(StorageRequestMessages.MSGS);
+      long msgToken = proto.start(StorageRequestMessage.FLAG_OVERRIDE_MESSAGE);
+      proto.write(StorageRequestMessage.FlagOverrideMessage.PACKAGE_NAME, packageName);
+      proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_NAME, flagName);
+      proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_VALUE, flagValue);
+      proto.write(StorageRequestMessage.FlagOverrideMessage.IS_LOCAL, isLocal);
+      proto.end(msgToken);
+      proto.end(msgsToken);
+    }
+
+    /**
+     * deserialize a flag input proto stream and log
+     * @param proto
+     */
+    static void parseAndLogAconfigdReturn(ProtoInputStream proto) throws IOException {
+        while (true) {
+          switch (proto.nextField()) {
+            case (int) StorageReturnMessages.MSGS:
+              long msgsToken = proto.start(StorageReturnMessages.MSGS);
+              switch (proto.nextField()) {
+                case (int) StorageReturnMessage.FLAG_OVERRIDE_MESSAGE:
+                  Slog.d(TAG, "successfully handled override requests");
+                  long msgToken = proto.start(StorageReturnMessage.FLAG_OVERRIDE_MESSAGE);
+                  proto.end(msgToken);
+                  break;
+                case (int) StorageReturnMessage.ERROR_MESSAGE:
+                  String errmsg = proto.readString(StorageReturnMessage.ERROR_MESSAGE);
+                  Slog.d(TAG, "override request failed: " + errmsg);
+                  break;
+                case ProtoInputStream.NO_MORE_FIELDS:
+                  break;
+                default:
+                  logErr("invalid message type, expecting only flag override return or error message");
+                  break;
+              }
+              proto.end(msgsToken);
+              break;
+            case ProtoInputStream.NO_MORE_FIELDS:
+              return;
+            default:
+              logErr("invalid message type, expect storage return message");
+              break;
+          }
+        }
+    }
+
+    /**
+     * apply flag local override in aconfig new storage
+     * @param props
+     */
+    static void setLocalOverridesInNewStorage(DeviceConfig.Properties props) {
+        int num_requests = 0;
+        ProtoOutputStream requests = new ProtoOutputStream();
+        for (String flagName : props.getKeyset()) {
+            String flagValue = props.getString(flagName, null);
+            if (flagName == null || flagValue == null) {
+                continue;
+            }
+
+            int idx = flagName.indexOf(":");
+            if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
+                logErr("invalid local flag override: " + flagName);
+                continue;
+            }
+            String actualNamespace = flagName.substring(0, idx);
+            String fullFlagName = flagName.substring(idx+1);
+            idx = fullFlagName.lastIndexOf(".");
+            if (idx == -1) {
+              logErr("invalid flag name: " + fullFlagName);
+              continue;
+            }
+            String packageName = fullFlagName.substring(0, idx);
+            String realFlagName = fullFlagName.substring(idx+1);
+            writeFlagOverrideRequest(requests, packageName, realFlagName, flagValue, true);
+            ++num_requests;
+        }
+
+        if (num_requests == 0) {
+          return;
+        }
+
+        // send requests to aconfigd and obtain the return byte buffer
+        ProtoInputStream returns = sendAconfigdRequests(requests);
+
+        // deserialize back using proto input stream
+        try {
+          parseAndLogAconfigdReturn(returns);
+        } catch (IOException ioe) {
+            logErr("failed to parse aconfigd return", ioe);
+        }
     }
 
     public static SettingsToPropertiesMapper start(ContentResolver contentResolver) {
@@ -391,7 +573,7 @@
         for (String property_name : property_names) {
             String[] segments = property_name.split("\\.");
             if (segments.length < 3) {
-                log("failed to extract category name from property " + property_name);
+                logErr("failed to extract category name from property " + property_name);
                 continue;
             }
             categories.add(segments[2]);
@@ -419,6 +601,48 @@
         return propertyName;
     }
 
+
+    /**
+     * stage flags in aconfig new storage
+     * @param propsToStage
+     */
+    @VisibleForTesting
+    static void stageFlagsInNewStorage(HashMap<String, HashMap<String, String>> propsToStage) {
+        // write aconfigd requests proto to proto output stream
+        int num_requests = 0;
+        ProtoOutputStream requests = new ProtoOutputStream();
+        for (HashMap.Entry<String, HashMap<String, String>> entry : propsToStage.entrySet()) {
+            String actualNamespace = entry.getKey();
+            HashMap<String, String> flagValuesToStage = entry.getValue();
+            for (String fullFlagName : flagValuesToStage.keySet()) {
+                String stagedValue = flagValuesToStage.get(fullFlagName);
+                int idx = fullFlagName.lastIndexOf(".");
+                if (idx == -1) {
+                    logErr("invalid flag name: " + fullFlagName);
+                    continue;
+                }
+                String packageName = fullFlagName.substring(0, idx);
+                String flagName = fullFlagName.substring(idx+1);
+                writeFlagOverrideRequest(requests, packageName, flagName, stagedValue, false);
+                ++num_requests;
+            }
+        }
+
+        if (num_requests == 0) {
+          return;
+        }
+
+        // send requests to aconfigd and obtain the return
+        ProtoInputStream returns = sendAconfigdRequests(requests);
+
+        // deserialize back using proto input stream
+        try {
+          parseAndLogAconfigdReturn(returns);
+        } catch (IOException ioe) {
+            logErr("failed to parse aconfigd return", ioe);
+        }
+    }
+
     /**
      * system property name constructing rule for aconfig flags:
      * "persist.device_config.aconfig_flags.[category_name].[flag_name]".
@@ -457,7 +681,7 @@
       for (String flagName : properties.getKeyset()) {
         int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER);
         if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
-          log("invalid staged flag: " + flagName);
+          logErr("invalid staged flag: " + flagName);
           continue;
         }
         String actualNamespace = flagName.substring(0, idx);
@@ -482,10 +706,10 @@
         for (String flagName : flagStagedValues.keySet()) {
           String stagedValue = flagStagedValues.get(flagName);
           String currentValue = flagCurrentValues.get(flagName);
-          if (currentValue == null) {
-            currentValue = new String("false");
+          if (stagedValue == null) {
+            continue;
           }
-          if (stagedValue != null && !stagedValue.equalsIgnoreCase(currentValue)) {
+          if (currentValue == null || !stagedValue.equalsIgnoreCase(currentValue)) {
             flagsToStage.put(flagName, stagedValue);
           }
         }
@@ -508,7 +732,7 @@
             }
             value = "";
         } else if (value.length() > SYSTEM_PROPERTY_MAX_LENGTH) {
-            log("key=" + key + " value=" + value + " exceeds system property max length.");
+            logErr("key=" + key + " value=" + value + " exceeds system property max length.");
             return;
         }
 
@@ -518,11 +742,11 @@
             // Failure to set a property can be caused by SELinux denial. This usually indicates
             // that the property wasn't allowlisted in sepolicy.
             // No need to report it on all user devices, only on debug builds.
-            log("Unable to set property " + key + " value '" + value + "'", e);
+            logErr("Unable to set property " + key + " value '" + value + "'", e);
         }
     }
 
-    private static void log(String msg, Exception e) {
+    private static void logErr(String msg, Exception e) {
         if (Build.IS_DEBUGGABLE) {
             Slog.wtf(TAG, msg, e);
         } else {
@@ -530,7 +754,7 @@
         }
     }
 
-    private static void log(String msg) {
+    private static void logErr(String msg) {
         if (Build.IS_DEBUGGABLE) {
             Slog.wtf(TAG, msg);
         } else {
@@ -548,7 +772,7 @@
 
             br.close();
         } catch (IOException ioe) {
-            log("failed to read file " + RESET_RECORD_FILE_PATH, ioe);
+            logErr("failed to read file " + RESET_RECORD_FILE_PATH, ioe);
         }
         return content;
     }
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index fb63ec6..b7108df 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -116,3 +116,10 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "skip_unimportant_connections"
+    namespace: "backstage_power"
+    description: "Avoid OomAdjuster calculations for connections that won't change importance"
+    bug: "323376416"
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 798aaee..ad93f6f 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -55,6 +55,7 @@
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM_OPS;
 import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE;
+import static android.app.AppOpsManager.UID_STATE_NONEXISTENT;
 import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
 import static android.app.AppOpsManager._NUM_OP;
 import static android.app.AppOpsManager.extractFlagsFromKey;
@@ -70,7 +71,6 @@
 import static android.content.Intent.EXTRA_REPLACING;
 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
 import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
-import static android.permission.flags.Flags.runtimePermissionAppopsMappingEnabled;
 
 import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
 
@@ -130,6 +130,7 @@
 import android.os.UserHandle;
 import android.os.storage.StorageManagerInternal;
 import android.permission.PermissionManager;
+import android.permission.flags.Flags;
 import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -140,6 +141,7 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
+import android.util.SparseLongArray;
 import android.util.TimeUtils;
 import android.util.Xml;
 
@@ -153,7 +155,6 @@
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IAppOpsStartedCallback;
 import com.android.internal.app.MessageSamplingConfig;
-import com.android.internal.camera.flags.Flags;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.os.Clock;
 import com.android.internal.pm.pkg.component.ParsedAttribution;
@@ -1102,6 +1103,7 @@
                         if (onModeChangedListeners == null) {
                             continue;
                         }
+                        onModeChangedListeners = new ArraySet<>(onModeChangedListeners);
                     }
                     for (int i = 0; i < changedUids.length; i++) {
                         final int changedUid = changedUids[i];
@@ -1254,7 +1256,9 @@
             for (int uidIdx = mUidStates.size() - 1; uidIdx >= 0; uidIdx--) {
                 int uid = mUidStates.keyAt(uidIdx);
                 if (knownUids.get(uid, false)) {
-                    if (uid >= Process.FIRST_APPLICATION_UID) {
+                    int appId = UserHandle.getAppId(uid);
+                    if (appId >= Process.FIRST_APPLICATION_UID
+                            && appId <= Process.LAST_APPLICATION_UID) {
                         ArrayMap<String, Ops> pkgOps = mUidStates.valueAt(uidIdx).pkgOps;
                         for (int pkgIdx = pkgOps.size() - 1; pkgIdx >= 0; pkgIdx--) {
                             String pkgName = pkgOps.keyAt(pkgIdx);
@@ -1418,6 +1422,9 @@
     // The callback method from AppOpsUidStateTracker
     private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) {
         synchronized (this) {
+            if (state == UID_STATE_NONEXISTENT) {
+                onUidProcessDeathLocked(uid);
+            }
             UidState uidState = getUidStateLocked(uid, false);
 
             boolean hasForegroundWatchers = false;
@@ -1505,6 +1512,11 @@
                 }
             }
 
+            if (state == UID_STATE_NONEXISTENT) {
+                // For UID_STATE_NONEXISTENT, we don't call onUidStateChanged for AttributedOps
+                return;
+            }
+
             if (uidState != null) {
                 int numPkgs = uidState.pkgOps.size();
                 for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
@@ -1529,6 +1541,81 @@
         }
     }
 
+    @GuardedBy("this")
+    private void onUidProcessDeathLocked(int uid) {
+        if (!mUidStates.contains(uid) || !Flags.finishRunningOpsForKilledPackages()) {
+            return;
+        }
+        final SparseLongArray chainsToFinish = new SparseLongArray();
+        doForAllAttributedOpsInUidLocked(uid, (attributedOp) -> {
+            attributedOp.doForAllInProgressStartOpEvents((event) -> {
+                int chainId = event.getAttributionChainId();
+                if (chainId != ATTRIBUTION_CHAIN_ID_NONE) {
+                    long currentEarliestStartTime =
+                            chainsToFinish.get(chainId, Long.MAX_VALUE);
+                    if (event.getStartTime() < currentEarliestStartTime) {
+                        // Store the earliest chain link we're finishing, so that we can go back
+                        // and finish any links in the chain that started after this one
+                        chainsToFinish.put(chainId, event.getStartTime());
+                    }
+                }
+                attributedOp.finished(event.getClientId());
+            });
+        });
+        finishChainsLocked(chainsToFinish);
+    }
+
+    @GuardedBy("this")
+    private void finishChainsLocked(SparseLongArray chainsToFinish) {
+        doForAllAttributedOpsLocked((attributedOp) -> {
+            attributedOp.doForAllInProgressStartOpEvents((event) -> {
+                int chainId = event.getAttributionChainId();
+                // If this event is part of a chain, and this event started after the event in the
+                // chain we already finished, then finish this event, too
+                long earliestEventStart = chainsToFinish.get(chainId, Long.MAX_VALUE);
+                if (chainId != ATTRIBUTION_CHAIN_ID_NONE
+                        && event.getStartTime() >= earliestEventStart) {
+                    attributedOp.finished(event.getClientId());
+                }
+            });
+        });
+    }
+
+    @GuardedBy("this")
+    private void doForAllAttributedOpsLocked(Consumer<AttributedOp> action) {
+        int numUids = mUidStates.size();
+        for (int uidNum = 0; uidNum < numUids; uidNum++) {
+            int uid = mUidStates.keyAt(uidNum);
+            doForAllAttributedOpsInUidLocked(uid, action);
+        }
+    }
+
+    @GuardedBy("this")
+    private void doForAllAttributedOpsInUidLocked(int uid, Consumer<AttributedOp> action) {
+        UidState uidState = mUidStates.get(uid);
+        if (uidState == null) {
+            return;
+        }
+
+        int numPkgs = uidState.pkgOps.size();
+        for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+            Ops ops = uidState.pkgOps.valueAt(pkgNum);
+            int numOps = ops.size();
+            for (int opNum = 0; opNum < numOps; opNum++) {
+                Op op = ops.valueAt(opNum);
+                int numDevices = op.mDeviceAttributedOps.size();
+                for (int deviceNum = 0; deviceNum < numDevices; deviceNum++) {
+                    ArrayMap<String, AttributedOp> attrOps =
+                            op.mDeviceAttributedOps.valueAt(deviceNum);
+                    int numAttributions = attrOps.size();
+                    for (int attrNum = 0; attrNum < numAttributions; attrNum++) {
+                        action.accept(attrOps.valueAt(attrNum));
+                    }
+                }
+            }
+        }
+    }
+
     /**
      * Notify the proc state or capability has changed for a certain UID.
      */
@@ -2699,7 +2786,7 @@
      * have information on them.
      */
     private static boolean isOpAllowedForUid(int uid) {
-        return runtimePermissionAppopsMappingEnabled()
+        return Flags.runtimePermissionAppopsMappingEnabled()
                 && (uid == Process.ROOT_UID || uid == Process.SYSTEM_UID);
     }
 
@@ -2909,10 +2996,12 @@
         final int proxyUid = attributionSource.getUid();
         final String proxyPackageName = attributionSource.getPackageName();
         final String proxyAttributionTag = attributionSource.getAttributionTag();
-        final int proxiedUid = attributionSource.getNextUid();
         final int proxyVirtualDeviceId = attributionSource.getDeviceId();
+
+        final int proxiedUid = attributionSource.getNextUid();
         final String proxiedPackageName = attributionSource.getNextPackageName();
         final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
+        final int proxiedVirtualDeviceId = attributionSource.getNextDeviceId();
 
         verifyIncomingProxyUid(attributionSource);
         verifyIncomingOp(code);
@@ -2949,7 +3038,8 @@
 
             final SyncNotedAppOp proxyReturn = noteOperationUnchecked(code, proxyUid,
                     resolveProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId,
-                    Process.INVALID_UID, null, null, proxyFlags, !isProxyTrusted,
+                    Process.INVALID_UID, null, null,
+                    Context.DEVICE_ID_DEFAULT, proxyFlags, !isProxyTrusted,
                     "proxy " + message, shouldCollectMessage);
             if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) {
                 return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag,
@@ -2967,9 +3057,9 @@
         final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
                 : AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED;
         return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
-                proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolveProxyPackageName,
-                proxyAttributionTag, proxiedFlags, shouldCollectAsyncNotedOp, message,
-                shouldCollectMessage);
+                proxiedAttributionTag, proxiedVirtualDeviceId, proxyUid, resolveProxyPackageName,
+                proxyAttributionTag, proxyVirtualDeviceId, proxiedFlags, shouldCollectAsyncNotedOp,
+                message, shouldCollectMessage);
     }
 
     @Override
@@ -3020,14 +3110,14 @@
         }
         return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
                 virtualDeviceId, Process.INVALID_UID, null, null,
-                AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp, message,
-                shouldCollectMessage);
+                Context.DEVICE_ID_DEFAULT, AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp,
+                message, shouldCollectMessage);
     }
 
     private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
             @Nullable String attributionTag, int virtualDeviceId, int proxyUid,
-            String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
-            boolean shouldCollectAsyncNotedOp, @Nullable String message,
+            String proxyPackageName, @Nullable String proxyAttributionTag, int proxyVirtualDeviceId,
+            @OpFlags int flags, boolean shouldCollectAsyncNotedOp, @Nullable String message,
             boolean shouldCollectMessage) {
         PackageVerificationResult pvr;
         try {
@@ -3158,8 +3248,9 @@
             }
             scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
                     virtualDeviceId, flags, AppOpsManager.MODE_ALLOWED);
+
             attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
-                    uidState.getState(), flags);
+                    getPersistentId(proxyVirtualDeviceId), uidState.getState(), flags);
 
             if (shouldCollectAsyncNotedOp) {
                 collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
@@ -3525,9 +3616,9 @@
         }
 
         return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
-                virtualDeviceId, Process.INVALID_UID, null, null, OP_FLAG_SELF,
-                startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
-                attributionFlags, attributionChainId);
+                virtualDeviceId, Process.INVALID_UID, null, null, Context.DEVICE_ID_DEFAULT,
+                OP_FLAG_SELF, startIfModeDefault, shouldCollectAsyncNotedOp, message,
+                shouldCollectMessage, attributionFlags, attributionChainId);
     }
 
     /** @deprecated Use {@link #startProxyOperationWithState} instead. */
@@ -3565,18 +3656,32 @@
         final int proxyUid = attributionSource.getUid();
         final String proxyPackageName = attributionSource.getPackageName();
         final String proxyAttributionTag = attributionSource.getAttributionTag();
-        final int proxiedUid = attributionSource.getNextUid();
         final int proxyVirtualDeviceId = attributionSource.getDeviceId();
+
+        final int proxiedUid = attributionSource.getNextUid();
         final String proxiedPackageName = attributionSource.getNextPackageName();
         final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
+        final int proxiedVirtualDeviceId = attributionSource.getNextDeviceId();
 
         verifyIncomingProxyUid(attributionSource);
         verifyIncomingOp(code);
         if (!isValidVirtualDeviceId(proxyVirtualDeviceId)) {
-            Slog.w(TAG, "startProxyOperationImpl returned MODE_IGNORED as virtualDeviceId "
-                    + proxyVirtualDeviceId + " is invalid");
-            return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag,
-                    proxiedPackageName);
+            Slog.w(
+                    TAG,
+                    "startProxyOperationImpl returned MODE_IGNORED as proxyVirtualDeviceId "
+                            + proxyVirtualDeviceId
+                            + " is invalid");
+            return new SyncNotedAppOp(
+                    AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, proxiedPackageName);
+        }
+        if (!isValidVirtualDeviceId(proxiedVirtualDeviceId)) {
+            Slog.w(
+                    TAG,
+                    "startProxyOperationImpl returned MODE_IGNORED as proxiedVirtualDeviceId "
+                            + proxiedVirtualDeviceId
+                            + " is invalid");
+            return new SyncNotedAppOp(
+                    AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, proxiedPackageName);
         }
         if (!isIncomingPackageValid(proxyPackageName, UserHandle.getUserId(proxyUid))
                 || !isIncomingPackageValid(proxiedPackageName, UserHandle.getUserId(proxiedUid))) {
@@ -3618,7 +3723,7 @@
             // Test if the proxied operation will succeed before starting the proxy operation
             final SyncNotedAppOp testProxiedOp = startOperationDryRun(code,
                     proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag,
-                    proxyVirtualDeviceId, resolvedProxyPackageName, proxiedFlags,
+                    proxiedVirtualDeviceId, resolvedProxyPackageName, proxiedFlags,
                     startIfModeDefault);
 
             if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
@@ -3630,7 +3735,7 @@
 
             final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
                     resolvedProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId,
-                    Process.INVALID_UID, null, null, proxyFlags,
+                    Process.INVALID_UID, null, null, Context.DEVICE_ID_DEFAULT, proxyFlags,
                     startIfModeDefault, !isProxyTrusted, "proxy " + message,
                     shouldCollectMessage, proxyAttributionFlags, attributionChainId);
             if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) {
@@ -3639,9 +3744,10 @@
         }
 
         return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
-                proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolvedProxyPackageName,
-                proxyAttributionTag, proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp,
-                message, shouldCollectMessage, proxiedAttributionFlags, attributionChainId);
+                proxiedAttributionTag, proxiedVirtualDeviceId, proxyUid, resolvedProxyPackageName,
+                proxyAttributionTag, proxyVirtualDeviceId, proxiedFlags, startIfModeDefault,
+                shouldCollectAsyncNotedOp, message, shouldCollectMessage, proxiedAttributionFlags,
+                attributionChainId);
     }
 
     private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
@@ -3651,9 +3757,10 @@
     private SyncNotedAppOp startOperationUnchecked(IBinder clientId, int code, int uid,
             @NonNull String packageName, @Nullable String attributionTag, int virtualDeviceId,
             int proxyUid, String proxyPackageName, @Nullable String proxyAttributionTag,
-            @OpFlags int flags, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
-            @Nullable String message, boolean shouldCollectMessage,
-            @AttributionFlags int attributionFlags, int attributionChainId) {
+            int proxyVirtualDeviceId, @OpFlags int flags, boolean startIfModeDefault,
+            boolean shouldCollectAsyncNotedOp, @Nullable String message,
+            boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
+            int attributionChainId) {
         PackageVerificationResult pvr;
         try {
             pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
@@ -3748,13 +3855,13 @@
                     + " flags: " + AppOpsManager.flagsToString(flags));
             try {
                 if (isRestricted) {
-                    attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
-                            proxyAttributionTag, virtualDeviceId, uidState.getState(), flags,
-                            attributionFlags, attributionChainId);
+                    attributedOp.createPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName,
+                            proxyAttributionTag, getPersistentId(proxyVirtualDeviceId),
+                            uidState.getState(), flags, attributionFlags, attributionChainId);
                 } else {
-                    attributedOp.started(clientId, proxyUid, proxyPackageName,
-                            proxyAttributionTag, virtualDeviceId, uidState.getState(), flags,
-                            attributionFlags, attributionChainId);
+                    attributedOp.started(clientId, virtualDeviceId, proxyUid, proxyPackageName,
+                            proxyAttributionTag, getPersistentId(proxyVirtualDeviceId),
+                            uidState.getState(), flags, attributionFlags, attributionChainId);
                     startType = START_TYPE_STARTED;
                 }
             } catch (RemoteException e) {
@@ -4752,8 +4859,8 @@
         if ((code == OP_CAMERA) && isAutomotive()) {
             final long identity = Binder.clearCallingIdentity();
             try {
-                if ((Flags.cameraPrivacyAllowlist())
-                        && (mSensorPrivacyManager.isCameraPrivacyEnabled(packageName))) {
+                if (com.android.internal.camera.flags.Flags.cameraPrivacyAllowlist()
+                        && mSensorPrivacyManager.isCameraPrivacyEnabled(packageName)) {
                     return true;
                 }
             } finally {
@@ -4943,7 +5050,7 @@
 
         if (accessTime > 0) {
             attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg,
-                    proxyAttributionTag, uidState, opFlags);
+                    proxyAttributionTag, PERSISTENT_DEVICE_ID_DEFAULT, uidState, opFlags);
         }
         if (rejectTime > 0) {
             attributedOp.rejected(rejectTime, uidState, opFlags);
@@ -6396,12 +6503,13 @@
     }
 
     private void notifyWatchersOnDefaultDevice(int code, int uid) {
-        final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
+        ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
         synchronized (this) {
             modeChangedListenerSet = mOpModeWatchers.get(code);
             if (modeChangedListenerSet == null) {
                 return;
             }
+            modeChangedListenerSet = new ArraySet<>(modeChangedListenerSet);
         }
         notifyOpChanged(modeChangedListenerSet,  code, uid, null, PERSISTENT_DEVICE_ID_DEFAULT);
     }
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java
index 18ea8cf..268b286 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java
@@ -68,6 +68,7 @@
             return UID_STATE_BACKGROUND;
         }
 
+        // UID_STATE_NONEXISTENT is deliberately excluded here
         return UID_STATE_CACHED;
     }
 
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index bc6ef20..03c8156 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -34,7 +34,9 @@
 import static android.app.AppOpsManager.OP_TAKE_AUDIO_FOCUS;
 import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE;
 import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
+import static android.app.AppOpsManager.UID_STATE_NONEXISTENT;
 import static android.app.AppOpsManager.UID_STATE_TOP;
+import static android.permission.flags.Flags.finishRunningOpsForKilledPackages;
 
 import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState;
 
@@ -343,13 +345,14 @@
         int capability = mCapability.get(uid, PROCESS_CAPABILITY_NONE);
         boolean appWidgetVisible = mAppWidgetVisible.get(uid, false);
 
+        boolean foregroundChange = uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
+                != pendingUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
+                || capability != pendingCapability
+                || appWidgetVisible != pendingAppWidgetVisible;
+
         if (uidState != pendingUidState
                 || capability != pendingCapability
                 || appWidgetVisible != pendingAppWidgetVisible) {
-            boolean foregroundChange = uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
-                    != pendingUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
-                    || capability != pendingCapability
-                    || appWidgetVisible != pendingAppWidgetVisible;
 
             if (foregroundChange) {
                 // To save on memory usage, log only interesting changes.
@@ -372,6 +375,16 @@
             mCapability.delete(uid);
             mAppWidgetVisible.delete(uid);
             mPendingGone.delete(uid);
+            if (finishRunningOpsForKilledPackages()) {
+                for (int i = 0; i < mUidStateChangedCallbacks.size(); i++) {
+                    UidStateChangedCallback cb = mUidStateChangedCallbacks.keyAt(i);
+                    Executor executor = mUidStateChangedCallbacks.valueAt(i);
+
+                    executor.execute(PooledLambda.obtainRunnable(
+                            UidStateChangedCallback::onUidStateChanged, cb, uid,
+                            UID_STATE_NONEXISTENT, foregroundChange));
+                }
+            }
         } else {
             mUidStates.put(uid, pendingUidState);
             mCapability.put(uid, pendingCapability);
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 2285826..02fc993 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -24,7 +24,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
-import android.companion.virtual.VirtualDeviceManager;
 import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteException;
@@ -39,6 +38,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.NoSuchElementException;
+import java.util.function.Consumer;
 
 final class AttributedOp {
     private final @NonNull AppOpsService mAppOpsService;
@@ -95,16 +95,17 @@
      *
      * @param proxyUid            The uid of the proxy
      * @param proxyPackageName    The package name of the proxy
-     * @param proxyAttributionTag the attributionTag in the proxies package
+     * @param proxyAttributionTag The attributionTag in the proxies package
+     * @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
      */
     public void accessed(int proxyUid, @Nullable String proxyPackageName,
-            @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
-            @AppOpsManager.OpFlags int flags) {
+            @Nullable String proxyAttributionTag, @Nullable String proxyDeviceId,
+            @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
         long accessTime = System.currentTimeMillis();
-        accessed(accessTime, -1, proxyUid, proxyPackageName,
-                proxyAttributionTag, uidState, flags);
+        accessed(accessTime, -1, proxyUid, proxyPackageName, proxyAttributionTag, proxyDeviceId,
+                uidState, flags);
 
         mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
                 parent.packageName, tag, uidState, flags, accessTime,
@@ -118,14 +119,16 @@
      * @param duration            The duration of the event
      * @param proxyUid            The uid of the proxy
      * @param proxyPackageName    The package name of the proxy
-     * @param proxyAttributionTag the attributionTag in the proxies package
+     * @param proxyAttributionTag The attributionTag in the proxies package
+     * @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
      */
     @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
     public void accessed(long noteTime, long duration, int proxyUid,
             @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
-            @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
+            @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState,
+            @AppOpsManager.OpFlags int flags) {
         long key = makeKey(uidState, flags);
 
         if (mAccessEvents == null) {
@@ -135,7 +138,7 @@
         AppOpsManager.OpEventProxyInfo proxyInfo = null;
         if (proxyUid != Process.INVALID_UID) {
             proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
-                            proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+                    proxyAttributionTag, proxyDeviceId);
         }
 
         AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key);
@@ -189,35 +192,36 @@
      * Update state when start was called
      *
      * @param clientId            Id of the startOp caller
+     * @param virtualDeviceId     The virtual device id of the startOp caller
      * @param proxyUid            The UID of the proxy app
      * @param proxyPackageName    The package name of the proxy app
      * @param proxyAttributionTag The attribution tag of the proxy app
+     * @param proxyDeviceId       The device id of the proxy app
      * @param uidState            UID state of the app startOp is called for
      * @param flags               The proxy flags
      * @param attributionFlags    The attribution flags associated with this operation.
-     * @param attributionChainId  The if of the attribution chain this operations is a part of.
+     * @param attributionChainId  The if of the attribution chain this operations is a part of
      */
-    public void started(@NonNull IBinder clientId, int proxyUid,
+    public void started(@NonNull IBinder clientId, int virtualDeviceId, int proxyUid,
             @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
-            int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
+            @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState,
             @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags,
             int attributionChainId) throws RemoteException {
-        startedOrPaused(clientId, proxyUid, proxyPackageName,
-                proxyAttributionTag, proxyVirtualDeviceId, uidState, flags,
-                /* triggeredByUidStateChange */ false, /* isStarted */ true, attributionFlags,
-                attributionChainId);
+        startedOrPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName, proxyAttributionTag,
+                proxyDeviceId, uidState, flags, attributionFlags, attributionChainId, false,
+                true);
     }
 
     @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
-    private void startedOrPaused(@NonNull IBinder clientId, int proxyUid,
+    private void startedOrPaused(@NonNull IBinder clientId, int virtualDeviceId, int proxyUid,
             @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
-            int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
-            @AppOpsManager.OpFlags int flags, boolean triggeredByUidStateChange,
-            boolean isStarted, @AppOpsManager.AttributionFlags int attributionFlags,
-            int attributionChainId) throws RemoteException {
+            @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState,
+            @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags,
+            int attributionChainId, boolean triggeredByUidStateChange, boolean isStarted)
+            throws RemoteException {
         if (!triggeredByUidStateChange && !parent.isRunning() && isStarted) {
             mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
-                    parent.packageName, tag, proxyVirtualDeviceId, true, attributionFlags,
+                    parent.packageName, tag, virtualDeviceId, true, attributionFlags,
                     attributionChainId);
         }
 
@@ -233,9 +237,9 @@
         InProgressStartOpEvent event = events.get(clientId);
         if (event == null) {
             event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime,
-                    SystemClock.elapsedRealtime(), clientId, tag, proxyVirtualDeviceId,
+                    SystemClock.elapsedRealtime(), clientId, tag, virtualDeviceId,
                     PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
-                    proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
+                    proxyUid, proxyPackageName, proxyAttributionTag, proxyDeviceId, uidState, flags,
                     attributionFlags, attributionChainId);
             events.put(clientId, event);
         } else {
@@ -253,6 +257,19 @@
         }
     }
 
+    public void doForAllInProgressStartOpEvents(Consumer<InProgressStartOpEvent> action) {
+        ArrayMap<IBinder, AttributedOp.InProgressStartOpEvent> events = isPaused()
+                ? mPausedInProgressEvents : mInProgressEvents;
+        if (events == null) {
+            return;
+        }
+
+        int numStartedOps = events.size();
+        for (int i = 0; i < numStartedOps; i++) {
+            action.accept(events.valueAt(i));
+        }
+    }
+
     /**
      * Update state when finishOp was called. Will finish started ops, and delete paused ops.
      *
@@ -366,15 +383,14 @@
     /**
      * Create an event that will be started, if the op is unpaused.
      */
-    public void createPaused(@NonNull IBinder clientId, int proxyUid,
-            @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
-            int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
-            @AppOpsManager.OpFlags int flags,
-            @AppOpsManager.AttributionFlags int attributionFlags,
+    public void createPaused(@NonNull IBinder clientId, int virtualDeviceId,
+            int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+            @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState,
+            @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags,
             int attributionChainId) throws RemoteException {
-        startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
-                proxyVirtualDeviceId, uidState, flags, false, false,
-                attributionFlags, attributionChainId);
+        startedOrPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName, proxyAttributionTag,
+                proxyDeviceId, uidState, flags, attributionFlags, attributionChainId, false,
+                false);
     }
 
     /**
@@ -496,16 +512,16 @@
                     // Call started() to add a new start event object and then add the
                     // previously removed unfinished start counts back
                     if (proxy != null) {
-                        startedOrPaused(event.getClientId(), proxy.getUid(),
-                                proxy.getPackageName(), proxy.getAttributionTag(),
-                                event.getVirtualDeviceId(), newState, event.getFlags(),
-                                true, isRunning,
-                                event.getAttributionFlags(), event.getAttributionChainId());
+                        startedOrPaused(event.getClientId(), event.getVirtualDeviceId(),
+                                proxy.getUid(), proxy.getPackageName(), proxy.getAttributionTag(),
+                                proxy.getDeviceId(), newState, event.getFlags(),
+                                event.getAttributionFlags(), event.getAttributionChainId(), true,
+                                isRunning);
                     } else {
-                        startedOrPaused(event.getClientId(), Process.INVALID_UID, null, null,
-                                event.getVirtualDeviceId(), newState, event.getFlags(), true,
-                                isRunning, event.getAttributionFlags(),
-                                event.getAttributionChainId());
+                        startedOrPaused(event.getClientId(), event.getVirtualDeviceId(),
+                                Process.INVALID_UID, null, null, null,
+                                newState, event.getFlags(), event.getAttributionFlags(),
+                                event.getAttributionChainId(), true, isRunning);
                     }
 
                     events = isRunning ? mInProgressEvents : mPausedInProgressEvents;
@@ -847,7 +863,8 @@
         InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId,
                 @Nullable String attributionTag, int virtualDeviceId,  @NonNull Runnable onDeath,
                 int proxyUid, @Nullable String proxyPackageName,
-                @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
+                @Nullable String proxyAttributionTag, @Nullable String proxyDeviceId,
+                @AppOpsManager.UidState int uidState,
                 @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags
                 int attributionFlags, int attributionChainId) throws RemoteException {
 
@@ -856,7 +873,7 @@
             AppOpsManager.OpEventProxyInfo proxyInfo = null;
             if (proxyUid != Process.INVALID_UID) {
                 proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
-                        proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+                        proxyAttributionTag, proxyDeviceId);
             }
 
             if (recycled != null) {
@@ -880,7 +897,8 @@
             super(maxUnusedPooledObjects);
         }
 
-        AppOpsManager.OpEventProxyInfo acquire(@IntRange(from = 0) int uid,
+        AppOpsManager.OpEventProxyInfo acquire(
+                @IntRange(from = 0) int uid,
                 @Nullable String packageName,
                 @Nullable String attributionTag,
                 @Nullable String deviceId) {
@@ -890,7 +908,7 @@
                 return recycled;
             }
 
-            return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag);
+            return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag, deviceId);
         }
     }
 }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index add8491..15c5c10 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4440,7 +4440,8 @@
                     || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING) {
                 voiceActive = true;
             }
-            if (usage == AudioAttributes.USAGE_MEDIA || usage == AudioAttributes.USAGE_GAME) {
+            if (usage == AudioAttributes.USAGE_MEDIA || usage == AudioAttributes.USAGE_GAME
+                    || usage == AudioAttributes.USAGE_UNKNOWN) {
                 mediaActive = true;
             }
         }
@@ -9778,9 +9779,9 @@
             mContentResolver.registerContentObserver(Settings.Global.getUriFor(
                 Settings.Global.DOCK_AUDIO_MEDIA_ENABLED), false, this);
             mContentResolver.registerContentObserver(Settings.System.getUriFor(
-                    Settings.System.MASTER_MONO), false, this);
+                    Settings.System.MASTER_MONO), false, this, UserHandle.USER_ALL);
             mContentResolver.registerContentObserver(Settings.System.getUriFor(
-                    Settings.System.MASTER_BALANCE), false, this);
+                    Settings.System.MASTER_BALANCE), false, this, UserHandle.USER_ALL);
 
             mEncodedSurroundMode = mSettings.getGlobalInt(
                     mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT,
diff --git a/services/core/java/com/android/server/audio/MusicFxHelper.java b/services/core/java/com/android/server/audio/MusicFxHelper.java
index ba45310..cf0b2ae 100644
--- a/services/core/java/com/android/server/audio/MusicFxHelper.java
+++ b/services/core/java/com/android/server/audio/MusicFxHelper.java
@@ -70,6 +70,8 @@
     // The binder token identifying the UidObserver registration.
     private IBinder mUidObserverToken = null;
 
+    private boolean mIsBound;
+
     // Package name and list of open audio sessions for this package
     private static class PackageSessions {
         String mPackageName;
@@ -109,6 +111,7 @@
                 if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
                     Intent bindIntent = new Intent().setClassName(mMusicFxPackageName,
                             "com.android.musicfx.KeepAliveService");
+                    mIsBound = true;
                     mContext.bindServiceAsUser(
                             bindIntent, mMusicFxBindConnection, Context.BIND_AUTO_CREATE,
                             UserHandle.of(getCurrentUserId()));
@@ -157,9 +160,12 @@
                     Log.e(TAG, "RemoteException with unregisterUidObserver: " + e);
                 }
                 mUidObserverToken = null;
-                mContext.unbindService(mMusicFxBindConnection);
-                Log.i(TAG, "last session closed, unregister UID observer, and unbind "
-                        + mMusicFxPackageName);
+                if (mIsBound) {
+                    mContext.unbindService(mMusicFxBindConnection);
+                    mIsBound = false;
+                    Log.i(TAG, "last session closed, unregister UID observer, and unbind "
+                            + mMusicFxPackageName);
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index e2c4b46..cae1695 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -347,9 +347,6 @@
     //------------------------------------------------------
     // routing monitoring
     synchronized void onRoutingUpdated() {
-        if (!mFeatureEnabled) {
-            return;
-        }
         switch (mState) {
             case STATE_UNINITIALIZED:
             case STATE_NOT_SUPPORTED:
@@ -393,7 +390,7 @@
             setDispatchAvailableState(false);
         }
 
-        boolean enabled = able && enabledAvailable.first;
+        boolean enabled = mFeatureEnabled && able && enabledAvailable.first;
         if (enabled) {
             loglogi("Enabling Spatial Audio since enabled for media device:"
                     + currentDevice);
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 11cca66..2a16872 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -298,7 +298,7 @@
                 return -1;
             }
 
-            if (promptInfo.containsTestConfigurations()) {
+            if (promptInfo.requiresTestOrInternalPermission()) {
                 if (getContext().checkCallingOrSelfPermission(TEST_BIOMETRIC)
                         != PackageManager.PERMISSION_GRANTED) {
                     checkInternalPermission();
@@ -306,10 +306,10 @@
             }
 
             // Only allow internal clients to enable non-public options.
-            if (promptInfo.containsPrivateApiConfigurations()) {
+            if (promptInfo.requiresInternalPermission()) {
                 checkInternalPermission();
             }
-            if (promptInfo.containsAdvancedApiConfigurations()) {
+            if (promptInfo.requiresAdvancedPermission()) {
                 checkBiometricAdvancedPermission();
             }
 
diff --git a/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java b/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java
new file mode 100644
index 0000000..7cf2d30
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java
@@ -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.server.biometrics;
+
+import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
+import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
+
+import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.BiometricNotificationUtils;
+
+/**
+ * Receives broadcast to biometrics dangling notification.
+ */
+public class BiometricDanglingReceiver extends BroadcastReceiver {
+    private static final String TAG = "BiometricDanglingReceiver";
+
+    public static final String ACTION_FINGERPRINT_RE_ENROLL_LAUNCH =
+            "action_fingerprint_re_enroll_launch";
+    public static final String ACTION_FINGERPRINT_RE_ENROLL_DISMISS =
+            "action_fingerprint_re_enroll_dismiss";
+
+    public static final String ACTION_FACE_RE_ENROLL_LAUNCH =
+            "action_face_re_enroll_launch";
+    public static final String ACTION_FACE_RE_ENROLL_DISMISS =
+            "action_face_re_enroll_dismiss";
+
+    public static final String FACE_SETTINGS_ACTION = "android.settings.FACE_SETTINGS";
+
+    private static final String SETTINGS_PACKAGE = "com.android.settings";
+
+    /**
+     * Constructor for BiometricDanglingReceiver.
+     *
+     * @param context context
+     * @param modality the value from BiometricsProtoEnums.MODALITY_*
+     */
+    public BiometricDanglingReceiver(@NonNull Context context, int modality) {
+        final IntentFilter intentFilter = new IntentFilter();
+        if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT) {
+            intentFilter.addAction(ACTION_FINGERPRINT_RE_ENROLL_LAUNCH);
+            intentFilter.addAction(ACTION_FINGERPRINT_RE_ENROLL_DISMISS);
+        } else if (modality == BiometricsProtoEnums.MODALITY_FACE) {
+            intentFilter.addAction(ACTION_FACE_RE_ENROLL_LAUNCH);
+            intentFilter.addAction(ACTION_FACE_RE_ENROLL_DISMISS);
+        }
+        context.registerReceiver(this, intentFilter, Context.RECEIVER_NOT_EXPORTED);
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Slog.d(TAG, "Received: " + intent.getAction());
+        if (ACTION_FINGERPRINT_RE_ENROLL_LAUNCH.equals(intent.getAction())) {
+            launchBiometricEnrollActivity(context, Settings.ACTION_FINGERPRINT_ENROLL);
+            BiometricNotificationUtils.cancelFingerprintReEnrollNotification(context);
+        } else if (ACTION_FINGERPRINT_RE_ENROLL_DISMISS.equals(intent.getAction())) {
+            BiometricNotificationUtils.cancelFingerprintReEnrollNotification(context);
+        } else if (ACTION_FACE_RE_ENROLL_LAUNCH.equals(intent.getAction())) {
+            launchBiometricEnrollActivity(context, FACE_SETTINGS_ACTION);
+            BiometricNotificationUtils.cancelFaceReEnrollNotification(context);
+        } else if (ACTION_FACE_RE_ENROLL_DISMISS.equals(intent.getAction())) {
+            BiometricNotificationUtils.cancelFaceReEnrollNotification(context);
+        }
+        context.unregisterReceiver(this);
+    }
+
+    private void launchBiometricEnrollActivity(Context context, String action) {
+        context.sendBroadcast(
+                new Intent(ACTION_CLOSE_SYSTEM_DIALOGS).setFlags(FLAG_RECEIVER_FOREGROUND));
+        final Intent intent = new Intent(action);
+        intent.setPackage(SETTINGS_PACKAGE);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(intent);
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
index 0e22f75..53e6bdb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
@@ -24,13 +24,18 @@
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.face.FaceEnrollOptions;
 import android.hardware.fingerprint.FingerprintEnrollOptions;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.text.BidiFormatter;
 import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.server.biometrics.BiometricDanglingReceiver;
+
+import java.util.List;
 
 /**
  * Biometric notification helper class.
@@ -39,6 +44,7 @@
 
     private static final String TAG = "BiometricNotificationUtils";
     private static final String FACE_RE_ENROLL_NOTIFICATION_TAG = "FaceReEnroll";
+    private static final String FINGERPRINT_RE_ENROLL_NOTIFICATION_TAG = "FingerprintReEnroll";
     private static final String BAD_CALIBRATION_NOTIFICATION_TAG = "FingerprintBadCalibration";
     private static final String KEY_RE_ENROLL_FACE = "re_enroll_face_unlock";
     private static final String FACE_SETTINGS_ACTION = "android.settings.FACE_SETTINGS";
@@ -50,6 +56,8 @@
     private static final String FACE_ENROLL_CHANNEL = "FaceEnrollNotificationChannel";
     private static final String FACE_RE_ENROLL_CHANNEL = "FaceReEnrollNotificationChannel";
     private static final String FINGERPRINT_ENROLL_CHANNEL = "FingerprintEnrollNotificationChannel";
+    private static final String FINGERPRINT_RE_ENROLL_CHANNEL =
+            "FingerprintReEnrollNotificationChannel";
     private static final String FINGERPRINT_BAD_CALIBRATION_CHANNEL =
             "FingerprintBadCalibrationNotificationChannel";
     private static final long NOTIFICATION_INTERVAL_MS = 24 * 60 * 60 * 1000;
@@ -177,10 +185,124 @@
                 BAD_CALIBRATION_NOTIFICATION_TAG, Notification.VISIBILITY_SECRET, false);
     }
 
+    /**
+     * Shows a biometric re-enroll notification.
+     */
+    public static void showBiometricReEnrollNotification(@NonNull Context context,
+            @NonNull List<String> identifiers, boolean allIdentifiersDeleted, int modality) {
+        final boolean isFingerprint = modality == BiometricsProtoEnums.MODALITY_FINGERPRINT;
+        final String reEnrollName = isFingerprint ? FINGERPRINT_RE_ENROLL_NOTIFICATION_TAG
+                : FACE_RE_ENROLL_NOTIFICATION_TAG;
+        if (identifiers.isEmpty()) {
+            Slog.v(TAG, "Skipping " + reEnrollName + " notification : empty list");
+            return;
+        }
+        Slog.d(TAG, "Showing " + reEnrollName + " notification :[" + identifiers.size()
+                + " identifier(s) deleted, allIdentifiersDeleted=" + allIdentifiersDeleted + "]");
+
+        final String name =
+                context.getString(R.string.device_unlock_notification_name);
+        final String title = context.getString(isFingerprint
+                ? R.string.fingerprint_dangling_notification_title
+                : R.string.face_dangling_notification_title);
+        final String content = isFingerprint
+                ? getFingerprintDanglingContentString(context, identifiers, allIdentifiersDeleted)
+                : context.getString(R.string.face_dangling_notification_msg);
+
+        // Create "Set up" notification action button.
+        final Intent setupIntent = new Intent(
+                isFingerprint ? BiometricDanglingReceiver.ACTION_FINGERPRINT_RE_ENROLL_LAUNCH
+                : BiometricDanglingReceiver.ACTION_FACE_RE_ENROLL_LAUNCH);
+        final PendingIntent setupPendingIntent = PendingIntent.getBroadcastAsUser(context, 0,
+                setupIntent, PendingIntent.FLAG_IMMUTABLE, UserHandle.CURRENT);
+        final String setupText =
+                context.getString(R.string.biometric_dangling_notification_action_set_up);
+        final Notification.Action setupAction = new Notification.Action.Builder(
+                null, setupText, setupPendingIntent).build();
+
+        // Create "Not now" notification action button.
+        final Intent notNowIntent = new Intent(
+                isFingerprint ? BiometricDanglingReceiver.ACTION_FINGERPRINT_RE_ENROLL_DISMISS
+                : BiometricDanglingReceiver.ACTION_FACE_RE_ENROLL_DISMISS);
+        final PendingIntent notNowPendingIntent = PendingIntent.getBroadcastAsUser(context, 0,
+                notNowIntent, PendingIntent.FLAG_IMMUTABLE, UserHandle.CURRENT);
+        final String notNowText = context.getString(
+                R.string.biometric_dangling_notification_action_not_now);
+        final Notification.Action notNowAction = new Notification.Action.Builder(
+                null, notNowText, notNowPendingIntent).build();
+
+        final String channel = isFingerprint ? FINGERPRINT_RE_ENROLL_CHANNEL
+                : FACE_RE_ENROLL_CHANNEL;
+        final String tag = isFingerprint ? FINGERPRINT_RE_ENROLL_NOTIFICATION_TAG
+                : FACE_RE_ENROLL_NOTIFICATION_TAG;
+
+        showNotificationHelper(context, name, title, content, setupPendingIntent, setupAction,
+                notNowAction, Notification.CATEGORY_SYSTEM, channel, tag,
+                Notification.VISIBILITY_SECRET, false, Notification.FLAG_NO_CLEAR);
+    }
+
+    private static String getFingerprintDanglingContentString(Context context,
+            @NonNull List<String> fingerprints, boolean allFingerprintDeleted) {
+        if (fingerprints.isEmpty()) {
+            return null;
+        }
+
+        final int resId;
+        final int size = fingerprints.size();
+        final StringBuilder first = new StringBuilder();
+        final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
+        if (size > 1) {
+            // If there are more than 1 fingerprint deleted, the "second" will be the last
+            // fingerprint and set the others to "first".
+            // For example, if we have 3 fingerprints deleted(fp1, fp2 and fp3):
+            //   first  = "fp1, fp2"
+            //   second = "fp3"
+            final String separator = ", ";
+            String second = null;
+            for (int i = 0; i < size; i++) {
+                if (i == size - 1) {
+                    second = bidiFormatter.unicodeWrap("\"" + fingerprints.get(i) + "\"");
+                } else {
+                    first.append(bidiFormatter.unicodeWrap("\""));
+                    first.append(bidiFormatter.unicodeWrap(fingerprints.get(i)));
+                    first.append(bidiFormatter.unicodeWrap("\""));
+                    if (i < size - 2) {
+                        first.append(bidiFormatter.unicodeWrap(separator));
+                    }
+                }
+            }
+            if (allFingerprintDeleted) {
+                resId = R.string.fingerprint_dangling_notification_msg_all_deleted_2;
+            } else {
+                resId = R.string.fingerprint_dangling_notification_msg_2;
+            }
+
+            return String.format(context.getString(resId), first, second);
+        } else {
+            if (allFingerprintDeleted) {
+                resId = R.string.fingerprint_dangling_notification_msg_all_deleted_1;
+            } else {
+                resId = R.string.fingerprint_dangling_notification_msg_1;
+            }
+            first.append(bidiFormatter.unicodeWrap("\""));
+            first.append(bidiFormatter.unicodeWrap(fingerprints.get(0)));
+            first.append(bidiFormatter.unicodeWrap("\""));
+            return String.format(context.getString(resId), first);
+        }
+    }
+
     private static void showNotificationHelper(Context context, String name, String title,
-                String content, PendingIntent pendingIntent, String category,
-                String channelName, String notificationTag, int visibility,
-                boolean listenToDismissEvent) {
+            String content, PendingIntent pendingIntent, String category, String channelName,
+            String notificationTag, int visibility, boolean listenToDismissEvent) {
+        showNotificationHelper(context, name, title, content, pendingIntent,
+                null /* positiveAction */, null /* negativeAction */, category, channelName,
+                notificationTag, visibility, listenToDismissEvent, 0);
+    }
+
+    private static void showNotificationHelper(Context context, String name, String title,
+            String content, PendingIntent pendingIntent, Notification.Action positiveAction,
+            Notification.Action negativeAction, String category, String channelName,
+            String notificationTag, int visibility, boolean listenToDismissEvent, int flags) {
         Slog.v(TAG," listenToDismissEvent = " + listenToDismissEvent);
         final PendingIntent dismissIntent = PendingIntent.getActivityAsUser(context,
                 0 /* requestCode */, DISMISS_FRR_INTENT, PendingIntent.FLAG_IMMUTABLE /* flags */,
@@ -202,6 +324,15 @@
                 .setContentIntent(pendingIntent)
                 .setVisibility(visibility);
 
+        if (flags > 0) {
+            builder.setFlag(flags, true);
+        }
+        if (positiveAction != null) {
+            builder.addAction(positiveAction);
+        }
+        if (negativeAction != null) {
+            builder.addAction(negativeAction);
+        }
         if (listenToDismissEvent) {
             builder.setDeleteIntent(dismissIntent);
         }
@@ -253,4 +384,14 @@
                 UserHandle.CURRENT);
     }
 
+    /**
+     * Cancels a fingerprint enrollment notification
+     */
+    public static void cancelFingerprintReEnrollNotification(@NonNull Context context) {
+        final NotificationManager notificationManager =
+                context.getSystemService(NotificationManager.class);
+        notificationManager.cancelAsUser(FINGERPRINT_RE_ENROLL_NOTIFICATION_TAG, NOTIFICATION_ID,
+                UserHandle.CURRENT);
+    }
+
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
index 6daaad1..81ab26d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
@@ -22,6 +22,7 @@
 import android.os.IBinder;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
@@ -44,6 +45,7 @@
     private List<? extends BiometricAuthenticator.Identifier> mEnrolledList;
     // List of templates to remove from the HAL
     private List<BiometricAuthenticator.Identifier> mUnknownHALTemplates = new ArrayList<>();
+    private final int mInitialEnrolledSize;
 
     protected InternalEnumerateClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, int userId, @NonNull String owner,
@@ -55,6 +57,7 @@
         super(context, lazyDaemon, token, null /* ClientMonitorCallbackConverter */, userId, owner,
                 0 /* cookie */, sensorId, logger, biometricContext);
         mEnrolledList = enrolledList;
+        mInitialEnrolledSize = mEnrolledList.size();
         mUtils = utils;
     }
 
@@ -111,8 +114,10 @@
 
         // At this point, mEnrolledList only contains templates known to the framework and
         // not the HAL.
+        final List<String> names = new ArrayList<>();
         for (int i = 0; i < mEnrolledList.size(); i++) {
             BiometricAuthenticator.Identifier identifier = mEnrolledList.get(i);
+            names.add(identifier.getName().toString());
             Slog.e(TAG, "doTemplateCleanup(): Removing dangling template from framework: "
                     + identifier.getBiometricId() + " " + identifier.getName());
             mUtils.removeBiometricForUser(getContext(),
@@ -120,6 +125,11 @@
 
             getLogger().logUnknownEnrollmentInFramework();
         }
+
+        // Send dangling notification.
+        if (!names.isEmpty()) {
+            sendDanglingNotification(names);
+        }
         mEnrolledList.clear();
     }
 
@@ -127,8 +137,24 @@
         return mUnknownHALTemplates;
     }
 
+    /**
+     * Send the dangling notification.
+     */
+    @VisibleForTesting
+    public void sendDanglingNotification(@NonNull List<String> identifierNames) {
+        if (!identifierNames.isEmpty()) {
+            Slog.e(TAG, "sendDanglingNotification(): initial enrolledSize="
+                    + mInitialEnrolledSize + ", after clean up size=" + mEnrolledList.size());
+            final boolean allIdentifiersDeleted = mEnrolledList.size() == mInitialEnrolledSize;
+            BiometricNotificationUtils.showBiometricReEnrollNotification(
+                    getContext(), identifierNames, allIdentifiersDeleted, getModality());
+        }
+    }
+
     @Override
     public int getProtoEnum() {
         return BiometricsProto.CM_ENUMERATE;
     }
+
+    protected abstract int getModality();
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java
index d85455e..6ce3bc5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java
@@ -18,12 +18,14 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.face.Face;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
@@ -35,7 +37,8 @@
 /**
  * Face-specific internal enumerate client for the {@link IFace} AIDL HAL interface.
  */
-class FaceInternalEnumerateClient extends InternalEnumerateClient<AidlSession> {
+@VisibleForTesting
+public class FaceInternalEnumerateClient extends InternalEnumerateClient<AidlSession> {
     private static final String TAG = "FaceInternalEnumerateClient";
 
     FaceInternalEnumerateClient(@NonNull Context context,
@@ -56,4 +59,9 @@
             mCallback.onClientFinished(this, false /* success */);
         }
     }
+
+    @Override
+    protected int getModality() {
+        return BiometricsProtoEnums.MODALITY_FACE;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index e71cffe..f0a4189 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -52,6 +52,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
 import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.BiometricDanglingReceiver;
 import com.android.server.biometrics.BiometricHandlerProvider;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
@@ -201,6 +202,7 @@
         mBiometricHandlerProvider = biometricHandlerProvider;
 
         initAuthenticationBroadcastReceiver();
+        initFaceDanglingBroadcastReceiver();
         initSensors(resetLockoutRequiresChallenge, props);
     }
 
@@ -214,6 +216,10 @@
                 });
     }
 
+    private void initFaceDanglingBroadcastReceiver() {
+        new BiometricDanglingReceiver(mContext, BiometricsProtoEnums.MODALITY_FACE);
+    }
+
     private void initSensors(boolean resetLockoutRequiresChallenge, SensorProps[] props) {
         if (resetLockoutRequiresChallenge) {
             Slog.d(getTag(), "Adding HIDL configs");
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java
index a5a832a..2849bd9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java
@@ -18,11 +18,13 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.fingerprint.Fingerprint;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.BiometricUtils;
@@ -35,7 +37,8 @@
  * Fingerprint-specific internal client supporting the
  * {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
  */
-class FingerprintInternalEnumerateClient extends InternalEnumerateClient<AidlSession> {
+@VisibleForTesting
+public class FingerprintInternalEnumerateClient extends InternalEnumerateClient<AidlSession> {
     private static final String TAG = "FingerprintInternalEnumerateClient";
 
     protected FingerprintInternalEnumerateClient(@NonNull Context context,
@@ -56,4 +59,9 @@
             mCallback.onClientFinished(this, false /* success */);
         }
     }
+
+    @Override
+    protected int getModality() {
+        return BiometricsProtoEnums.MODALITY_FINGERPRINT;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 6874c71..c0dcd49 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -58,6 +58,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
 import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.BiometricDanglingReceiver;
 import com.android.server.biometrics.BiometricHandlerProvider;
 import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
@@ -205,6 +206,7 @@
         mBiometricHandlerProvider = biometricHandlerProvider;
 
         initAuthenticationBroadcastReceiver();
+        initFingerprintDanglingBroadcastReceiver();
         initSensors(resetLockoutRequiresHardwareAuthToken, props, gestureAvailabilityDispatcher);
     }
 
@@ -218,6 +220,10 @@
                 });
     }
 
+    private void initFingerprintDanglingBroadcastReceiver() {
+        new BiometricDanglingReceiver(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+    }
+
     private void initSensors(boolean resetLockoutRequiresHardwareAuthToken, SensorProps[] props,
             GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
         if (!resetLockoutRequiresHardwareAuthToken) {
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 645a366..390ee96 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -37,6 +37,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.res.Configuration;
+import android.graphics.ImageFormat;
 import android.graphics.Rect;
 import android.hardware.CameraExtensionSessionStats;
 import android.hardware.CameraSessionStats;
@@ -906,6 +907,7 @@
 
             int extensionType = FrameworkStatsLog.CAMERA_ACTION_EVENT__EXT_TYPE__EXTENSION_NONE;
             boolean extensionIsAdvanced = false;
+            int extensionCaptureFormat = ImageFormat.UNKNOWN;
             if (e.mExtSessionStats != null) {
                 switch (e.mExtSessionStats.type) {
                     case CameraExtensionSessionStats.Type.EXTENSION_AUTOMATIC:
@@ -932,6 +934,9 @@
                         Slog.w(TAG, "Unknown extension type: " + e.mExtSessionStats.type);
                 }
                 extensionIsAdvanced = e.mExtSessionStats.isAdvanced;
+                if (Flags.analytics24q3()) {
+                    extensionCaptureFormat = e.mExtSessionStats.captureFormat;
+                }
             }
 
             int streamCount = 0;
@@ -945,10 +950,13 @@
                 String zoomOverrideDebug = Flags.logZoomOverrideUsage()
                         ? ", zoomOverrideUsage " + e.mUsedZoomOverride
                         : "";
-
                 String mostRequestedFpsRangeDebug = Flags.analytics24q3()
                         ? ", mostRequestedFpsRange " + e.mMostRequestedFpsRange
                         : "";
+                String extensionCaptureFormatDebug = Flags.analytics24q3()
+                        ? " extensionCaptureFormat " + e.mExtSessionStats.captureFormat
+                        : "";
+
                 Slog.v(TAG, "CAMERA_ACTION_EVENT: action " + e.mAction
                         + " clientName " + e.mClientName
                         + ", duration " + e.getDuration()
@@ -971,8 +979,10 @@
                         + ", logId " + e.mLogId
                         + ", sessionIndex " + e.mSessionIndex
                         + ", mExtSessionStats {type " + extensionType
-                        + " isAdvanced " + extensionIsAdvanced + "}");
+                        + " isAdvanced " + extensionIsAdvanced
+                        + extensionCaptureFormatDebug + "}");
             }
+
             // Convert from CameraStreamStats to CameraStreamProto
             CameraStreamProto[] streamProtos = new CameraStreamProto[MAX_STREAM_STATISTICS];
             for (int i = 0; i < MAX_STREAM_STATISTICS; i++) {
@@ -1035,7 +1045,8 @@
                     e.mLogId, e.mSessionIndex,
                     extensionType, extensionIsAdvanced, e.mUsedUltraWide,
                     e.mUsedZoomOverride,
-                    e.mMostRequestedFpsRange.getLower(), e.mMostRequestedFpsRange.getUpper());
+                    e.mMostRequestedFpsRange.getLower(), e.mMostRequestedFpsRange.getUpper(),
+                    extensionCaptureFormat);
         }
     }
 
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index d9c3ab8..30d12e6 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -1189,7 +1189,11 @@
         update();
     }
 
-    void switchMode(@AutomaticBrightnessMode int mode) {
+    /**
+     * Responsible for switching the AutomaticBrightnessMode of the associated display. Also takes
+     * care of resetting the short term model wherever required
+     */
+    public void switchMode(@AutomaticBrightnessMode int mode) {
         if (!mBrightnessMappingStrategyMap.contains(mode)) {
             return;
         }
diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java
index 22f3bbd..fa8299b 100644
--- a/services/core/java/com/android/server/display/DisplayControl.java
+++ b/services/core/java/com/android/server/display/DisplayControl.java
@@ -29,7 +29,7 @@
  */
 public class DisplayControl {
     private static native IBinder nativeCreateDisplay(String name, boolean secure,
-            float requestedRefreshRate);
+            String uniqueId, float requestedRefreshRate);
     private static native void nativeDestroyDisplay(IBinder displayToken);
     private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes);
     private static native long[] nativeGetPhysicalDisplayIds();
@@ -43,20 +43,21 @@
     /**
      * Create a display in SurfaceFlinger.
      *
-     * @param name The name of the display
+     * @param name The name of the display.
      * @param secure Whether this display is secure.
      * @return The token reference for the display in SurfaceFlinger.
      */
     public static IBinder createDisplay(String name, boolean secure) {
         Objects.requireNonNull(name, "name must not be null");
-        return nativeCreateDisplay(name, secure, 0.0f);
+        return nativeCreateDisplay(name, secure, "", 0.0f);
     }
 
     /**
      * Create a display in SurfaceFlinger.
      *
-     * @param name The name of the display
+     * @param name The name of the display.
      * @param secure Whether this display is secure.
+     * @param uniqueId The unique ID for the display.
      * @param requestedRefreshRate The requested refresh rate in frames per second.
      * For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on
      * 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded
@@ -65,9 +66,10 @@
      * @return The token reference for the display in SurfaceFlinger.
      */
     public static IBinder createDisplay(String name, boolean secure,
-            float requestedRefreshRate) {
+            String uniqueId, float requestedRefreshRate) {
         Objects.requireNonNull(name, "name must not be null");
-        return nativeCreateDisplay(name, secure, requestedRefreshRate);
+        Objects.requireNonNull(uniqueId, "uniqueId must not be null");
+        return nativeCreateDisplay(name, secure, uniqueId, requestedRefreshRate);
     }
 
     /**
@@ -79,7 +81,6 @@
         if (displayToken == null) {
             throw new IllegalArgumentException("displayToken must not be null");
         }
-
         nativeDestroyDisplay(displayToken);
     }
 
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index d7a7dd4..0fcdf19 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -84,6 +84,7 @@
 import com.android.server.display.brightness.DisplayBrightnessController;
 import com.android.server.display.brightness.clamper.BrightnessClamperController;
 import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy2;
+import com.android.server.display.brightness.strategy.DisplayBrightnessStrategyConstants;
 import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
 import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
 import com.android.server.display.config.HysteresisLevels;
@@ -797,7 +798,7 @@
                 return;
             }
             mDisplayStateController.overrideDozeScreenState(displayState, reason);
-            sendUpdatePowerState();
+            updatePowerState();
         }, mClock.uptimeMillis());
     }
 
@@ -1333,12 +1334,6 @@
                 mDisplayStateController.shouldPerformScreenOffTransition());
         state = mPowerState.getScreenState();
 
-        // Switch to doze auto-brightness mode if needed
-        if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null
-                && !mAutomaticBrightnessController.isInIdleMode()) {
-            mAutomaticBrightnessController.switchMode(Display.isDozeState(state)
-                    ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
-        }
 
         DisplayBrightnessState displayBrightnessState = mDisplayBrightnessController
                 .updateBrightness(mPowerRequest, state);
@@ -1372,6 +1367,13 @@
         final boolean wasShortTermModelActive =
                 mAutomaticBrightnessStrategy.isShortTermModelActive();
         if (!mFlags.isRefactorDisplayPowerControllerEnabled()) {
+            // Switch to doze auto-brightness mode if needed
+            if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null
+                    && !mAutomaticBrightnessController.isInIdleMode()) {
+                mAutomaticBrightnessController.switchMode(Display.isDozeState(state)
+                        ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
+            }
+
             mAutomaticBrightnessStrategy.setAutoBrightnessState(state,
                     mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig(),
                     mBrightnessReasonTemp.getReason(), mPowerRequest.policy,
@@ -1440,35 +1442,54 @@
             brightnessState = clampScreenBrightness(brightnessState);
         }
 
-        // If there's an offload session, we need to set the initial doze brightness before
-        // the offload session starts controlling the brightness.
-        if (Float.isNaN(brightnessState) && mFlags.isDisplayOffloadEnabled()
-                && Display.isDozeState(state) && mDisplayOffloadSession != null) {
-            if (mAutomaticBrightnessController != null
-                    && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) {
-                // Use the auto-brightness curve and the last observed lux
-                rawBrightnessState = mAutomaticBrightnessController
-                        .getAutomaticScreenBrightnessBasedOnLastUsedLux(
-                                mTempBrightnessEvent);
-            } else {
-                rawBrightnessState = getDozeBrightnessForOffload();
-                mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
-                        | BrightnessEvent.FLAG_DOZE_SCALE);
+        if (Display.isDozeState(state)) {
+            // If there's an offload session, we need to set the initial doze brightness before
+            // the offload session starts controlling the brightness.
+            // During the transition DOZE_SUSPEND -> DOZE -> DOZE_SUSPEND, this brightness strategy
+            // will be selected again, meaning that no new brightness will be sent to the hardware
+            // and the display will stay at the brightness level set by the offload session.
+            if ((Float.isNaN(brightnessState)
+                    || displayBrightnessState.getDisplayBrightnessStrategyName()
+                    .equals(DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME))
+                    && mFlags.isDisplayOffloadEnabled()
+                    && mDisplayOffloadSession != null) {
+                if (mAutomaticBrightnessController != null
+                        && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) {
+                    // Use the auto-brightness curve and the last observed lux
+                    rawBrightnessState = mAutomaticBrightnessController
+                            .getAutomaticScreenBrightnessBasedOnLastUsedLux(
+                                    mTempBrightnessEvent);
+                } else {
+                    rawBrightnessState = getDozeBrightnessForOffload();
+                    mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
+                            | BrightnessEvent.FLAG_DOZE_SCALE);
+                }
+
+                if (BrightnessUtils.isValidBrightnessValue(rawBrightnessState)) {
+                    brightnessState = clampScreenBrightness(rawBrightnessState);
+                    mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_INITIAL);
+
+                    if (mAutomaticBrightnessController != null
+                            && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) {
+                        // Keep the brightness in the setting so that we can use it after the screen
+                        // turns on, until a lux sample becomes available. We don't do this when
+                        // auto-brightness is disabled - in that situation we still want to use
+                        // the last brightness from when the screen was on.
+                        updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
+                    }
+                }
             }
 
-            if (BrightnessUtils.isValidBrightnessValue(rawBrightnessState)) {
+            // Use default brightness when dozing unless overridden.
+            if (Float.isNaN(brightnessState)
+                    || displayBrightnessState.getDisplayBrightnessStrategyName()
+                    .equals(DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME)) {
+                rawBrightnessState = mScreenBrightnessDozeConfig;
                 brightnessState = clampScreenBrightness(rawBrightnessState);
-                mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_INITIAL);
+                mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT);
             }
         }
 
-        // Use default brightness when dozing unless overridden.
-        if (Float.isNaN(brightnessState) && Display.isDozeState(state)) {
-            rawBrightnessState = mScreenBrightnessDozeConfig;
-            brightnessState = clampScreenBrightness(rawBrightnessState);
-            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT);
-        }
-
         if (!mFlags.isRefactorDisplayPowerControllerEnabled()) {
             // The ALS is not available yet - use the screen off sensor to determine the initial
             // brightness
@@ -1490,7 +1511,7 @@
         }
 
         // Apply manual brightness.
-        if (Float.isNaN(brightnessState)) {
+        if (Float.isNaN(brightnessState) && !mFlags.isRefactorDisplayPowerControllerEnabled()) {
             rawBrightnessState = currentBrightnessSetting;
             brightnessState = clampScreenBrightness(rawBrightnessState);
             if (brightnessState != currentBrightnessSetting) {
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index bcdb442..a29e852 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -92,8 +92,9 @@
             Context context, Handler handler, Listener listener, DisplayManagerFlags featureFlags) {
         this(syncRoot, context, handler, listener, new SurfaceControlDisplayFactory() {
             @Override
-            public IBinder createDisplay(String name, boolean secure, float requestedRefreshRate) {
-                return DisplayControl.createDisplay(name, secure, requestedRefreshRate);
+            public IBinder createDisplay(String name, boolean secure, String uniqueId,
+                                         float requestedRefreshRate) {
+                return DisplayControl.createDisplay(name, secure, uniqueId, requestedRefreshRate);
             }
 
             @Override
@@ -126,7 +127,7 @@
         String name = virtualDisplayConfig.getName();
         boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0;
 
-        IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure,
+        IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure, uniqueId,
                 virtualDisplayConfig.getRequestedRefreshRate());
         MediaProjectionCallback mediaProjectionCallback =  null;
         if (projection != null) {
@@ -653,8 +654,9 @@
         /**
          * Create a virtual display in SurfaceFlinger.
          *
-         * @param name The name of the display
+         * @param name The name of the display.
          * @param secure Whether this display is secure.
+         * @param uniqueId The unique ID for the display.
          * @param requestedRefreshRate
          *     The refresh rate, frames per second, to request on the virtual display.
          *     It should be a divisor of refresh rate of the leader physical display
@@ -663,8 +665,9 @@
          *     the refresh rate of the leader physical display.
          * @return The token reference for the display in SurfaceFlinger.
          */
-        IBinder createDisplay(String name, boolean secure, float requestedRefreshRate);
-        
+        IBinder createDisplay(String name, boolean secure, String uniqueId,
+                              float requestedRefreshRate);
+
         /**
          * Destroy a display in SurfaceFlinger.
          *
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 22a21a6..feec4e6 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -32,6 +32,7 @@
 import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
 import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
 import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
+import com.android.server.display.brightness.strategy.FallbackBrightnessStrategy;
 import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy;
 import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy;
 import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy;
@@ -85,6 +86,9 @@
     @Nullable
     private final AutoBrightnessFallbackStrategy mAutoBrightnessFallbackStrategy;
 
+    @Nullable
+    private final FallbackBrightnessStrategy mFallbackBrightnessStrategy;
+
     // A collective representation of all the strategies that the selector is aware of. This is
     // non null, but the strategies this is tracking can be null
     @NonNull
@@ -118,7 +122,8 @@
         mInvalidBrightnessStrategy = injector.getInvalidBrightnessStrategy();
         mAutomaticBrightnessStrategy1 =
                 (!mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) ? null
-                        : injector.getAutomaticBrightnessStrategy1(context, displayId);
+                        : injector.getAutomaticBrightnessStrategy1(context, displayId,
+                                mDisplayManagerFlags);
         mAutomaticBrightnessStrategy2 =
                 (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) ? null
                         : injector.getAutomaticBrightnessStrategy2(context, displayId);
@@ -134,11 +139,14 @@
         } else {
             mOffloadBrightnessStrategy = null;
         }
+        mFallbackBrightnessStrategy = (mDisplayManagerFlags
+                .isRefactorDisplayPowerControllerEnabled())
+                ? injector.getFallbackBrightnessStrategy() : null;
         mDisplayBrightnessStrategies = new DisplayBrightnessStrategy[]{mInvalidBrightnessStrategy,
                 mScreenOffBrightnessStrategy, mDozeBrightnessStrategy, mFollowerBrightnessStrategy,
                 mBoostBrightnessStrategy, mOverrideBrightnessStrategy, mTemporaryBrightnessStrategy,
                 mAutomaticBrightnessStrategy1, mOffloadBrightnessStrategy,
-                mAutoBrightnessFallbackStrategy};
+                mAutoBrightnessFallbackStrategy, mFallbackBrightnessStrategy};
         mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean(
                 R.bool.config_allowAutoBrightnessWhileDozing);
         mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName();
@@ -179,6 +187,12 @@
             displayBrightnessStrategy = mOffloadBrightnessStrategy;
         } else if (isAutoBrightnessFallbackStrategyValid()) {
             displayBrightnessStrategy = mAutoBrightnessFallbackStrategy;
+        } else {
+            // This will become the ultimate fallback strategy once the flag has been fully rolled
+            // out
+            if (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) {
+                displayBrightnessStrategy = mFallbackBrightnessStrategy;
+            }
         }
 
         if (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) {
@@ -330,8 +344,8 @@
         }
 
         AutomaticBrightnessStrategy getAutomaticBrightnessStrategy1(Context context,
-                int displayId) {
-            return new AutomaticBrightnessStrategy(context, displayId);
+                int displayId, DisplayManagerFlags displayManagerFlags) {
+            return new AutomaticBrightnessStrategy(context, displayId, displayManagerFlags);
         }
 
         AutomaticBrightnessStrategy2 getAutomaticBrightnessStrategy2(Context context,
@@ -347,5 +361,9 @@
         AutoBrightnessFallbackStrategy getAutoBrightnessFallbackStrategy() {
             return new AutoBrightnessFallbackStrategy(/* injector= */ null);
         }
+
+        FallbackBrightnessStrategy getFallbackBrightnessStrategy() {
+            return new FallbackBrightnessStrategy();
+        }
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index a46975fb..11ef577 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -285,7 +285,8 @@
             List<BrightnessStateModifier> modifiers = new ArrayList<>();
             modifiers.add(new DisplayDimModifier(context));
             modifiers.add(new BrightnessLowPowerModeModifier());
-            if (flags.isEvenDimmerEnabled() && displayDeviceConfig != null) {
+            if (flags.isEvenDimmerEnabled() && displayDeviceConfig != null
+                    && displayDeviceConfig.isEvenDimmerAvailable()) {
                 modifiers.add(new BrightnessLowLuxModifier(handler, listener, context,
                         displayDeviceConfig));
             }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
index a3dfe22..7ba4a4d 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
@@ -87,9 +87,7 @@
                 mContentResolver, Settings.Secure.EVEN_DIMMER_MIN_NITS,
                 /* def= */ MIN_NITS_DEFAULT, userId);
 
-        boolean isActive = Settings.Secure.getFloatForUser(mContentResolver,
-                Settings.Secure.EVEN_DIMMER_ACTIVATED,
-                /* def= */ 0, userId) == 1.0f && mAutoBrightnessEnabled;
+        boolean isActive = isSettingEnabled() && mAutoBrightnessEnabled;
 
         float luxBasedNitsLowerBound = mDisplayDeviceConfig.getMinNitsFromLux(mAmbientLux);
 
@@ -202,6 +200,17 @@
         pw.println("  mMinNitsAllowed=" + mMinNitsAllowed);
     }
 
+    /**
+     * Defaults to true, on devices where setting is unset.
+     *
+     * @return if setting indicates feature is enabled
+     */
+    private boolean isSettingEnabled() {
+        return Settings.Secure.getFloatForUser(mContentResolver,
+                Settings.Secure.EVEN_DIMMER_ACTIVATED,
+                /* def= */ 1.0f, UserHandle.USER_CURRENT) == 1.0f;
+    }
+
     private float getBrightnessFromNits(float nits) {
         return mDisplayDeviceConfig.getBrightnessFromBacklight(
                 mDisplayDeviceConfig.getBacklightFromNits(nits));
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index 2305228..f809a49 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -17,6 +17,9 @@
 
 import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
 
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
+
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.display.BrightnessConfiguration;
@@ -33,6 +36,7 @@
 import com.android.server.display.brightness.BrightnessUtils;
 import com.android.server.display.brightness.StrategyExecutionRequest;
 import com.android.server.display.brightness.StrategySelectionNotifyRequest;
+import com.android.server.display.feature.DisplayManagerFlags;
 
 import java.io.PrintWriter;
 
@@ -98,19 +102,24 @@
 
     private Injector mInjector;
 
+    private DisplayManagerFlags mDisplayManagerFlags;
+
     @VisibleForTesting
-    AutomaticBrightnessStrategy(Context context, int displayId, Injector injector) {
+    AutomaticBrightnessStrategy(Context context, int displayId, Injector injector,
+            DisplayManagerFlags displayManagerFlags) {
         super(context, displayId);
         mContext = context;
         mDisplayId = displayId;
         mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
         mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
         mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        mDisplayManagerFlags  = displayManagerFlags;
         mInjector = (injector == null) ? new RealInjector() : injector;
     }
 
-    public AutomaticBrightnessStrategy(Context context, int displayId) {
-        this(context, displayId, null);
+    public AutomaticBrightnessStrategy(Context context, int displayId,
+            DisplayManagerFlags displayManagerFlags) {
+        this(context, displayId, null, displayManagerFlags);
     }
 
     /**
@@ -120,6 +129,7 @@
     public void setAutoBrightnessState(int targetDisplayState,
             boolean allowAutoBrightnessWhileDozingConfig, int brightnessReason, int policy,
             float lastUserSetScreenBrightness, boolean userSetBrightnessChanged) {
+        switchMode(targetDisplayState);
         final boolean autoBrightnessEnabledInDoze =
                 allowAutoBrightnessWhileDozingConfig && policy == POLICY_DOZE;
         mIsAutoBrightnessEnabled = shouldUseAutoBrightness()
@@ -479,6 +489,16 @@
         }
     }
 
+
+    private void switchMode(int state) {
+        if (mDisplayManagerFlags.areAutoBrightnessModesEnabled()
+                && mAutomaticBrightnessController != null
+                && !mAutomaticBrightnessController.isInIdleMode()) {
+            mAutomaticBrightnessController.switchMode(Display.isDozeState(state)
+                    ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
+        }
+    }
+
     /**
      * Evaluates if there are any temporary auto-brightness adjustments which is not applied yet.
      * Temporary brightness adjustments happen when the user moves the brightness slider in the
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategyConstants.java b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategyConstants.java
index 504683a..7b2f2b9 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategyConstants.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategyConstants.java
@@ -18,4 +18,5 @@
 
 public class DisplayBrightnessStrategyConstants {
     static final String INVALID_BRIGHTNESS_STRATEGY_NAME = "InvalidBrightnessStrategy";
+    public static final String FALLBACK_BRIGHTNESS_STRATEGY_NAME = "FallbackBrightnessStrategy";
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java
new file mode 100644
index 0000000..3463649a
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java
@@ -0,0 +1,72 @@
+/*
+ * 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.server.display.brightness.strategy;
+
+import android.annotation.NonNull;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.StrategyExecutionRequest;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
+
+import java.io.PrintWriter;
+
+/**
+ * Manages the brightness of the associated display when no other strategy qualifies for
+ * setting up the brightness state. This strategy is also being used for evaluating the
+ * display brightness state when we have a manually set brightness. This is a temporary state, and
+ * the logic for evaluating the manual brightness will be moved to a separate strategy
+ */
+public class FallbackBrightnessStrategy implements DisplayBrightnessStrategy{
+    @Override
+    public DisplayBrightnessState updateBrightness(
+            StrategyExecutionRequest strategyExecutionRequest) {
+        BrightnessReason brightnessReason = new BrightnessReason();
+        brightnessReason.setReason(BrightnessReason.REASON_MANUAL);
+        return new DisplayBrightnessState.Builder()
+                .setBrightness(strategyExecutionRequest.getCurrentScreenBrightness())
+                .setSdrBrightness(strategyExecutionRequest.getCurrentScreenBrightness())
+                .setBrightnessReason(brightnessReason)
+                .setDisplayBrightnessStrategyName(getName())
+                // The fallback brightness might change due to clamping. Make sure we tell the rest
+                // of the system by updating the setting
+                .setShouldUpdateScreenBrightnessSetting(true)
+                .build();
+    }
+
+    @NonNull
+    @Override
+    public String getName() {
+        return DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME;
+    }
+
+    @Override
+    public int getReason() {
+        return BrightnessReason.REASON_MANUAL;
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+
+    }
+
+    @Override
+    public void strategySelectionPostProcessor(
+            StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+
+    }
+}
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 0bb93a9..3883604 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -78,6 +78,7 @@
 import com.android.server.DisplayThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.accessibility.Flags;
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.twilight.TwilightListener;
 import com.android.server.twilight.TwilightManager;
@@ -356,6 +357,11 @@
                             case Secure.ACCESSIBILITY_DISPLAY_DALTONIZER:
                                 onAccessibilityDaltonizerChanged();
                                 break;
+                            case Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL:
+                                if (Flags.enableColorCorrectionSaturation()) {
+                                    onAccessibilityDaltonizerChanged();
+                                }
+                                break;
                             case Secure.DISPLAY_WHITE_BALANCE_ENABLED:
                                 updateDisplayWhiteBalanceStatus();
                                 break;
@@ -398,6 +404,11 @@
                 false /* notifyForDescendants */, mContentObserver, mCurrentUser);
         cr.registerContentObserver(Secure.getUriFor(Secure.REDUCE_BRIGHT_COLORS_LEVEL),
                 false /* notifyForDescendants */, mContentObserver, mCurrentUser);
+        if (Flags.enableColorCorrectionSaturation()) {
+            cr.registerContentObserver(
+                    Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL),
+                    false /* notifyForDescendants */, mContentObserver, mCurrentUser);
+        }
 
         // Apply the accessibility settings first, since they override most other settings.
         onAccessibilityInversionChanged();
@@ -597,21 +608,31 @@
         if (mCurrentUser == UserHandle.USER_NULL) {
             return;
         }
+        var contentResolver = getContext().getContentResolver();
         final int daltonizerMode = isAccessiblityDaltonizerEnabled()
-                ? Secure.getIntForUser(getContext().getContentResolver(),
-                    Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
-                    AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY, mCurrentUser)
+                ? Secure.getIntForUser(contentResolver,
+                Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
+                AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY, mCurrentUser)
                 : AccessibilityManager.DALTONIZER_DISABLED;
 
+        int saturation = NOT_SET;
+        if (Flags.enableColorCorrectionSaturation()) {
+            saturation = Secure.getIntForUser(
+                    contentResolver,
+                    Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
+                    NOT_SET,
+                    mCurrentUser);
+        }
+
         final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
         if (daltonizerMode == AccessibilityManager.DALTONIZER_SIMULATE_MONOCHROMACY) {
             // Monochromacy isn't supported by the native Daltonizer implementation; use grayscale.
             dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_GRAYSCALE,
                     MATRIX_GRAYSCALE);
-            dtm.setDaltonizerMode(AccessibilityManager.DALTONIZER_DISABLED);
+            dtm.setDaltonizerMode(AccessibilityManager.DALTONIZER_DISABLED, saturation);
         } else {
             dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_GRAYSCALE, null);
-            dtm.setDaltonizerMode(daltonizerMode);
+            dtm.setDaltonizerMode(daltonizerMode, saturation);
         }
     }
 
diff --git a/services/core/java/com/android/server/display/color/DisplayTransformManager.java b/services/core/java/com/android/server/display/color/DisplayTransformManager.java
index 0dba9e1..a76c427 100644
--- a/services/core/java/com/android/server/display/color/DisplayTransformManager.java
+++ b/services/core/java/com/android/server/display/color/DisplayTransformManager.java
@@ -16,6 +16,7 @@
 
 package com.android.server.display.color;
 
+import android.annotation.IntRange;
 import android.app.ActivityTaskManager;
 import android.hardware.display.ColorDisplayManager;
 import android.opengl.Matrix;
@@ -111,9 +112,15 @@
     /**
      * Lock used for synchronize access to {@link #mDaltonizerMode}.
      */
-    private final Object mDaltonizerModeLock = new Object();
+    @VisibleForTesting
+    final Object mDaltonizerModeLock = new Object();
+    @VisibleForTesting
     @GuardedBy("mDaltonizerModeLock")
-    private int mDaltonizerMode = -1;
+    int mDaltonizerMode = -1;
+
+    @VisibleForTesting
+    @GuardedBy("mDaltonizerModeLock")
+    int mDaltonizerLevel = -1;
 
     private static final IBinder sFlinger = ServiceManager.getService(SURFACE_FLINGER);
 
@@ -168,12 +175,15 @@
      * various types of color blindness.
      *
      * @param mode the new Daltonization mode, or -1 to disable
+     * @param level the level of saturation for color correction [-1,10] inclusive. -1 for when
+     *              it is not set.
      */
-    public void setDaltonizerMode(int mode) {
+    public void setDaltonizerMode(int mode, @IntRange(from = -1, to = 10) int level) {
         synchronized (mDaltonizerModeLock) {
-            if (mDaltonizerMode != mode) {
+            if (mDaltonizerMode != mode || mDaltonizerLevel != level) {
                 mDaltonizerMode = mode;
-                applyDaltonizerMode(mode);
+                mDaltonizerLevel = level;
+                applyDaltonizerMode(mode, level);
             }
         }
     }
@@ -223,10 +233,11 @@
     /**
      * Propagates the provided Daltonization mode to the SurfaceFlinger.
      */
-    private static void applyDaltonizerMode(int mode) {
+    private static void applyDaltonizerMode(int mode, int level) {
         final Parcel data = Parcel.obtain();
         data.writeInterfaceToken("android.ui.ISurfaceComposer");
         data.writeInt(mode);
+        data.writeInt(level);
         try {
             sFlinger.transact(SURFACE_FLINGER_TRANSACTION_DALTONIZER, data, null, 0);
         } catch (RemoteException ex) {
diff --git a/services/core/java/com/android/server/display/config/RefreshRateData.java b/services/core/java/com/android/server/display/config/RefreshRateData.java
index b186fd5..d7ed904 100644
--- a/services/core/java/com/android/server/display/config/RefreshRateData.java
+++ b/services/core/java/com/android/server/display/config/RefreshRateData.java
@@ -20,6 +20,10 @@
 import android.content.res.Resources;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Collections;
+import java.util.List;
 
 /**
  * RefreshRates config for display
@@ -58,12 +62,17 @@
      */
     public final int defaultRefreshRateInHbmSunlight;
 
+    public final List<SupportedModeData> lowPowerSupportedModes;
+
+    @VisibleForTesting
     public RefreshRateData(int defaultRefreshRate, int defaultPeakRefreshRate,
-            int defaultRefreshRateInHbmHdr, int defaultRefreshRateInHbmSunlight) {
+            int defaultRefreshRateInHbmHdr, int defaultRefreshRateInHbmSunlight,
+            List<SupportedModeData> lowPowerSupportedModes) {
         this.defaultRefreshRate = defaultRefreshRate;
         this.defaultPeakRefreshRate = defaultPeakRefreshRate;
         this.defaultRefreshRateInHbmHdr = defaultRefreshRateInHbmHdr;
         this.defaultRefreshRateInHbmSunlight = defaultRefreshRateInHbmSunlight;
+        this.lowPowerSupportedModes = Collections.unmodifiableList(lowPowerSupportedModes);
     }
 
 
@@ -71,9 +80,10 @@
     public String toString() {
         return "RefreshRateData {"
                 + "defaultRefreshRate: " + defaultRefreshRate
-                + "defaultPeakRefreshRate: " + defaultPeakRefreshRate
-                + "defaultRefreshRateInHbmHdr: " + defaultRefreshRateInHbmHdr
-                + "defaultRefreshRateInHbmSunlight: " + defaultRefreshRateInHbmSunlight
+                + ", defaultPeakRefreshRate: " + defaultPeakRefreshRate
+                + ", defaultRefreshRateInHbmHdr: " + defaultRefreshRateInHbmHdr
+                + ", defaultRefreshRateInHbmSunlight: " + defaultRefreshRateInHbmSunlight
+                + ", lowPowerSupportedModes=" + lowPowerSupportedModes
                 + "} ";
     }
 
@@ -90,8 +100,13 @@
         int defaultRefreshRateInHbmSunlight = loadDefaultRefreshRateInHbmSunlight(
                 refreshRateConfigs, resources);
 
+        NonNegativeFloatToFloatMap modes =
+                refreshRateConfigs == null ? null : refreshRateConfigs.getLowPowerSupportedModes();
+        List<SupportedModeData> lowPowerSupportedModes = SupportedModeData.load(modes);
+
         return new RefreshRateData(defaultRefreshRate, defaultPeakRefreshRate,
-                defaultRefreshRateInHbmHdr, defaultRefreshRateInHbmSunlight);
+                defaultRefreshRateInHbmHdr, defaultRefreshRateInHbmSunlight,
+                lowPowerSupportedModes);
     }
 
     private static int loadDefaultRefreshRate(
diff --git a/services/core/java/com/android/server/display/config/SensorData.java b/services/core/java/com/android/server/display/config/SensorData.java
index 6ad13c3..8bfc4a3 100644
--- a/services/core/java/com/android/server/display/config/SensorData.java
+++ b/services/core/java/com/android/server/display/config/SensorData.java
@@ -24,7 +24,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.feature.DisplayManagerFlags;
 
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -42,7 +41,7 @@
     public final String name;
     public final float minRefreshRate;
     public final float maxRefreshRate;
-    public final List<SupportedMode> supportedModes;
+    public final List<SupportedModeData> supportedModes;
 
     @VisibleForTesting
     public SensorData() {
@@ -61,7 +60,7 @@
 
     @VisibleForTesting
     public SensorData(String type, String name, float minRefreshRate, float maxRefreshRate,
-            List<SupportedMode> supportedModes) {
+            List<SupportedModeData> supportedModes) {
         this.type = type;
         this.name = name;
         this.minRefreshRate = minRefreshRate;
@@ -214,26 +213,11 @@
             minRefreshRate = rr.getMinimum().floatValue();
             maxRefreshRate = rr.getMaximum().floatValue();
         }
-        ArrayList<SupportedMode> supportedModes = new ArrayList<>();
-        NonNegativeFloatToFloatMap configSupportedModes = sensorDetails.getSupportedModes();
-        if (configSupportedModes != null) {
-            for (NonNegativeFloatToFloatPoint supportedMode : configSupportedModes.getPoint()) {
-                supportedModes.add(new SupportedMode(supportedMode.getFirst().floatValue(),
-                        supportedMode.getSecond().floatValue()));
-            }
-        }
+        List<SupportedModeData> supportedModes = SupportedModeData.load(
+                sensorDetails.getSupportedModes());
 
         return new SensorData(sensorDetails.getType(), sensorDetails.getName(), minRefreshRate,
                 maxRefreshRate, supportedModes);
     }
 
-    public static class SupportedMode {
-        public final float refreshRate;
-        public final float vsyncRate;
-
-        public SupportedMode(float refreshRate, float vsyncRate) {
-            this.refreshRate = refreshRate;
-            this.vsyncRate = vsyncRate;
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/display/config/SupportedModeData.java b/services/core/java/com/android/server/display/config/SupportedModeData.java
new file mode 100644
index 0000000..3c82884
--- /dev/null
+++ b/services/core/java/com/android/server/display/config/SupportedModeData.java
@@ -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.server.display.config;
+
+import android.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Supported display mode data. Display mode is uniquely identified by refreshRate-vsync pair
+ */
+public class SupportedModeData {
+    public final float refreshRate;
+    public final float vsyncRate;
+
+    public SupportedModeData(float refreshRate, float vsyncRate) {
+        this.refreshRate = refreshRate;
+        this.vsyncRate = vsyncRate;
+    }
+
+    @Override
+    public String toString() {
+        return "SupportedModeData{"
+                + "refreshRate= " + refreshRate
+                + ", vsyncRate= " + vsyncRate
+                + '}';
+    }
+
+    static List<SupportedModeData> load(@Nullable NonNegativeFloatToFloatMap configMap) {
+        ArrayList<SupportedModeData> supportedModes = new ArrayList<>();
+        if (configMap != null) {
+            for (NonNegativeFloatToFloatPoint supportedMode : configMap.getPoint()) {
+                supportedModes.add(new SupportedModeData(supportedMode.getFirst().floatValue(),
+                        supportedMode.getSecond().floatValue()));
+            }
+        }
+        return supportedModes;
+    }
+}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 91bd80e..846ee23 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -78,6 +78,7 @@
 import com.android.server.display.DisplayDeviceConfig;
 import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint;
 import com.android.server.display.config.RefreshRateData;
+import com.android.server.display.config.SupportedModeData;
 import com.android.server.display.feature.DeviceConfigParameterProvider;
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.utils.AmbientFilter;
@@ -451,15 +452,6 @@
         return config != null && config.isVrrSupportEnabled();
     }
 
-    private boolean isVrrSupportedByAnyDisplayLocked() {
-        for (int i = 0; i < mDisplayDeviceConfigByDisplay.size(); i++) {
-            if (mDisplayDeviceConfigByDisplay.valueAt(i).isVrrSupportEnabled()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     /**
      * Sets the desiredDisplayModeSpecsListener for changes to display mode and refresh rate ranges.
      */
@@ -939,18 +931,44 @@
         private final Uri mMatchContentFrameRateSetting =
                 Settings.Secure.getUriFor(Settings.Secure.MATCH_CONTENT_FRAME_RATE);
 
-        private final boolean mVsynLowPowerVoteEnabled;
+        private final boolean mVsyncLowPowerVoteEnabled;
         private final boolean mPeakRefreshRatePhysicalLimitEnabled;
 
         private final Context mContext;
+        private final Handler mHandler;
         private float mDefaultPeakRefreshRate;
         private float mDefaultRefreshRate;
+        private boolean mIsLowPower = false;
+
+        private final DisplayManager.DisplayListener mDisplayListener =
+                new DisplayManager.DisplayListener() {
+                    @Override
+                    public void onDisplayAdded(int displayId) {
+                        synchronized (mLock) {
+                            updateLowPowerModeAllowedModesLocked();
+                        }
+                    }
+
+                    @Override
+                    public void onDisplayRemoved(int displayId) {
+                        mVotesStorage.updateVote(displayId, Vote.PRIORITY_LOW_POWER_MODE_MODES,
+                                null);
+                    }
+
+                    @Override
+                    public void onDisplayChanged(int displayId) {
+                        synchronized (mLock) {
+                            updateLowPowerModeAllowedModesLocked();
+                        }
+                    }
+                };
 
         SettingsObserver(@NonNull Context context, @NonNull Handler handler,
                 DisplayManagerFlags flags) {
             super(handler);
             mContext = context;
-            mVsynLowPowerVoteEnabled = flags.isVsyncLowPowerVoteEnabled();
+            mHandler = handler;
+            mVsyncLowPowerVoteEnabled = flags.isVsyncLowPowerVoteEnabled();
             mPeakRefreshRatePhysicalLimitEnabled = flags.isPeakRefreshRatePhysicalLimitEnabled();
             // We don't want to load from the DeviceConfig while constructing since this leads to
             // a spike in the latency of DisplayManagerService startup. This happens because
@@ -983,6 +1001,7 @@
                     UserHandle.USER_SYSTEM);
             cr.registerContentObserver(mMatchContentFrameRateSetting, false /*notifyDescendants*/,
                     this);
+            mInjector.registerDisplayListener(mDisplayListener, mHandler);
 
             float deviceConfigDefaultPeakRefresh =
                     mConfigParameterProvider.getPeakRefreshRateDefault();
@@ -995,6 +1014,7 @@
                 updateLowPowerModeSettingLocked();
                 updateModeSwitchingTypeSettingLocked();
             }
+
         }
 
         public void setDefaultRefreshRate(float refreshRate) {
@@ -1061,23 +1081,36 @@
         }
 
         private void updateLowPowerModeSettingLocked() {
-            boolean inLowPowerMode = Settings.Global.getInt(mContext.getContentResolver(),
+            mIsLowPower = Settings.Global.getInt(mContext.getContentResolver(),
                     Settings.Global.LOW_POWER_MODE, 0 /*default*/) != 0;
             final Vote vote;
-            if (inLowPowerMode && mVsynLowPowerVoteEnabled && isVrrSupportedByAnyDisplayLocked()) {
-                vote = Vote.forSupportedRefreshRates(List.of(
-                        new SupportedRefreshRatesVote.RefreshRates(/* peakRefreshRate= */ 60f,
-                                /* vsyncRate= */ 240f),
-                        new SupportedRefreshRatesVote.RefreshRates(/* peakRefreshRate= */ 60f,
-                                /* vsyncRate= */ 60f)
-                ));
-            } else if (inLowPowerMode) {
+            if (mIsLowPower) {
                 vote = Vote.forRenderFrameRates(0f, 60f);
             } else {
                 vote = null;
             }
-            mVotesStorage.updateGlobalVote(Vote.PRIORITY_LOW_POWER_MODE, vote);
-            mBrightnessObserver.onLowPowerModeEnabledLocked(inLowPowerMode);
+            mVotesStorage.updateGlobalVote(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, vote);
+            mBrightnessObserver.onLowPowerModeEnabledLocked(mIsLowPower);
+            updateLowPowerModeAllowedModesLocked();
+        }
+
+        private void updateLowPowerModeAllowedModesLocked() {
+            if (!mVsyncLowPowerVoteEnabled) {
+                return;
+            }
+            if (mIsLowPower) {
+                for (int i = 0; i < mDisplayDeviceConfigByDisplay.size(); i++) {
+                    DisplayDeviceConfig config = mDisplayDeviceConfigByDisplay.valueAt(i);
+                    List<SupportedModeData> supportedModes = config
+                            .getRefreshRateData().lowPowerSupportedModes;
+                    mVotesStorage.updateVote(
+                            mDisplayDeviceConfigByDisplay.keyAt(i),
+                            Vote.PRIORITY_LOW_POWER_MODE_MODES,
+                            Vote.forSupportedRefreshRates(supportedModes));
+                }
+            } else {
+                mVotesStorage.removeAllVotesForPriority(Vote.PRIORITY_LOW_POWER_MODE_MODES);
+            }
         }
 
         /**
diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java
index ddb334e..8167c1f 100644
--- a/services/core/java/com/android/server/display/mode/Vote.java
+++ b/services/core/java/com/android/server/display/mode/Vote.java
@@ -18,6 +18,9 @@
 
 import android.annotation.NonNull;
 
+import com.android.server.display.config.SupportedModeData;
+
+import java.util.ArrayList;
 import java.util.List;
 
 interface Vote {
@@ -102,9 +105,15 @@
     // For internal application to limit display modes to specific ids
     int PRIORITY_SYSTEM_REQUESTED_MODES = 14;
 
-    // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if
+    // PRIORITY_LOW_POWER_MODE_MODES limits display modes to specific refreshRate-vsync pairs if
     // Settings.Global.LOW_POWER_MODE is on.
-    int PRIORITY_LOW_POWER_MODE = 15;
+    // Lower priority that PRIORITY_LOW_POWER_MODE_RENDER_RATE and if discarded (due to other
+    // higher priority votes), render rate limit can still apply
+    int PRIORITY_LOW_POWER_MODE_MODES = 14;
+
+    // PRIORITY_LOW_POWER_MODE_RENDER_RATE force the render frame rate to [0, 60HZ] if
+    // Settings.Global.LOW_POWER_MODE is on.
+    int PRIORITY_LOW_POWER_MODE_RENDER_RATE = 15;
 
     // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
     // higher priority voters' result is a range, it will fix the rate to a single choice.
@@ -177,22 +186,26 @@
         return new BaseModeRefreshRateVote(baseModeRefreshRate);
     }
 
-    static Vote forSupportedRefreshRates(
-            List<SupportedRefreshRatesVote.RefreshRates> refreshRates) {
-        return new SupportedRefreshRatesVote(refreshRates);
+    static Vote forSupportedRefreshRates(List<SupportedModeData> supportedModes) {
+        if (supportedModes.isEmpty()) {
+            return null;
+        }
+        List<SupportedRefreshRatesVote.RefreshRates> rates = new ArrayList<>();
+        for (SupportedModeData data : supportedModes) {
+            rates.add(new SupportedRefreshRatesVote.RefreshRates(data.refreshRate, data.vsyncRate));
+        }
+        return new SupportedRefreshRatesVote(rates);
     }
 
     static Vote forSupportedModes(List<Integer> modeIds) {
         return new SupportedModesVote(modeIds);
     }
 
-
-
     static Vote forSupportedRefreshRatesAndDisableSwitching(
             List<SupportedRefreshRatesVote.RefreshRates> supportedRefreshRates) {
         return new CombinedVote(
                 List.of(forDisableRefreshRateSwitching(),
-                        forSupportedRefreshRates(supportedRefreshRates)));
+                        new SupportedRefreshRatesVote(supportedRefreshRates)));
     }
 
     static String priorityToString(int priority) {
@@ -213,8 +226,10 @@
                 return "PRIORITY_HIGH_BRIGHTNESS_MODE";
             case PRIORITY_PROXIMITY:
                 return "PRIORITY_PROXIMITY";
-            case PRIORITY_LOW_POWER_MODE:
-                return "PRIORITY_LOW_POWER_MODE";
+            case PRIORITY_LOW_POWER_MODE_MODES:
+                return "PRIORITY_LOW_POWER_MODE_MODES";
+            case PRIORITY_LOW_POWER_MODE_RENDER_RATE:
+                return "PRIORITY_LOW_POWER_MODE_RENDER_RATE";
             case PRIORITY_SKIN_TEMPERATURE:
                 return "PRIORITY_SKIN_TEMPERATURE";
             case PRIORITY_UDFPS:
@@ -227,6 +242,8 @@
                 return "PRIORITY_LIMIT_MODE";
             case PRIORITY_SYNCHRONIZED_REFRESH_RATE:
                 return "PRIORITY_SYNCHRONIZED_REFRESH_RATE";
+            case PRIORITY_USER_SETTING_PEAK_REFRESH_RATE:
+                return "PRIORITY_USER_SETTING_PEAK_REFRESH_RATE";
             case PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE:
                 return "PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE";
             case PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE:
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index 816242d..c6aef7f 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -249,14 +249,14 @@
         mCurrentDream.mAppTask = appTask;
     }
 
-    void setDreamHasFocus(boolean hasFocus) {
+    void setDreamIsObscured(boolean isObscured) {
         if (mCurrentDream != null) {
-            mCurrentDream.mDreamHasFocus = hasFocus;
+            mCurrentDream.mDreamIsObscured = isObscured;
         }
     }
 
-    boolean dreamHasFocus() {
-        return mCurrentDream != null && mCurrentDream.mDreamHasFocus;
+    boolean dreamIsFrontmost() {
+        return mCurrentDream != null && mCurrentDream.dreamIsFrontmost();
     }
 
     /**
@@ -451,7 +451,7 @@
         private String mStopReason;
         private long mDreamStartTime;
         public boolean mWakingGently;
-        public boolean mDreamHasFocus;
+        private boolean mDreamIsObscured;
 
         private final Runnable mStopPreviousDreamsIfNeeded = this::stopPreviousDreamsIfNeeded;
         private final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded;
@@ -549,5 +549,9 @@
                 mHandler.removeCallbacks(mReleaseWakeLockIfNeeded);
             }
         }
+
+        boolean dreamIsFrontmost() {
+            return !mDreamIsObscured;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 2def5ae..18a9986 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -20,7 +20,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.service.dreams.Flags.dreamTracksFocus;
+import static android.service.dreams.Flags.dreamHandlesBeingObscured;
 
 import static com.android.server.wm.ActivityInterceptorCallback.DREAM_MANAGER_ORDERED_ID;
 
@@ -428,7 +428,7 @@
             // Can't start dreaming if we are already dreaming and the dream has focus. If we are
             // dreaming but the dream does not have focus, then the dream can be brought to the
             // front so it does have focus.
-            if (isScreenOn && isDreamingInternal() && dreamHasFocus()) {
+            if (isScreenOn && isDreamingInternal() && dreamIsFrontmost()) {
                 return false;
             }
 
@@ -463,9 +463,10 @@
         }
     }
 
-    private boolean dreamHasFocus() {
-        // Dreams always had focus before they were able to track it.
-        return !dreamTracksFocus() || mController.dreamHasFocus();
+    private boolean dreamIsFrontmost() {
+        // Dreams were always considered frontmost before they began tracking whether they are
+        // obscured.
+        return !dreamHandlesBeingObscured() || mController.dreamIsFrontmost();
     }
 
     protected void requestStartDreamFromShell() {
@@ -473,7 +474,7 @@
     }
 
     private void requestDreamInternal() {
-        if (isDreamingInternal() && !dreamHasFocus() && mController.bringDreamToFront()) {
+        if (isDreamingInternal() && !dreamIsFrontmost() && mController.bringDreamToFront()) {
             return;
         }
 
@@ -1159,10 +1160,16 @@
         }
 
         @Override
-        public void onDreamFocusChanged(boolean hasFocus) {
+        public void setDreamIsObscured(boolean isObscured) {
+            if (!dreamHandlesBeingObscured()) {
+                return;
+            }
+
+            checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
+
             final long ident = Binder.clearCallingIdentity();
             try {
-                mController.setDreamHasFocus(hasFocus);
+                mHandler.post(() -> mController.setDreamIsObscured(isObscured));
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
diff --git a/services/core/java/com/android/server/flags/pinner.aconfig b/services/core/java/com/android/server/flags/pinner.aconfig
index 16a45cd..2f817db 100644
--- a/services/core/java/com/android/server/flags/pinner.aconfig
+++ b/services/core/java/com/android/server/flags/pinner.aconfig
@@ -6,4 +6,11 @@
     namespace: "system_performance"
     description: "This flag controls if webview should be pinned in memory."
     bug: "307594624"
+}
+
+flag {
+    name: "skip_home_art_pins"
+    namespace: "system_performance"
+    description: "Ablation study flag that controls if home app odex/vdex files should be pinned in memory."
+    bug: "340935152"
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index d21fc85..5db17bb 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -29,7 +29,6 @@
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.SystemProperties;
-import android.os.UserHandle;
 import android.sysprop.HdmiProperties;
 import android.util.Slog;
 
@@ -278,8 +277,7 @@
     void dismissUiOnActiveSourceStatusRecovered() {
         assertRunOnServiceThread();
         Intent intent = new Intent(HdmiControlManager.ACTION_ON_ACTIVE_SOURCE_RECOVERED_DISMISS_UI);
-        mService.getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
-                HdmiControlService.PERMISSION);
+        mService.sendBroadcastAsUser(intent);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 46061a5..275c930 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -206,6 +206,10 @@
         launchDeviceDiscovery();
         startQueuedActions();
         if (!mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
+            if (hasAction(RequestActiveSourceAction.class)) {
+                Slog.i(TAG, "RequestActiveSourceAction is in progress. Restarting.");
+                removeAction(RequestActiveSourceAction.class);
+            }
             addAndStartAction(new RequestActiveSourceAction(this, new IHdmiControlCallback.Stub() {
                 @Override
                 public void onComplete(int result) {
@@ -1308,6 +1312,8 @@
                 mService.sendCecCommand(
                         HdmiCecMessageBuilder.buildActiveSource(
                                 getDeviceInfo().getLogicalAddress(), activePath));
+                updateActiveSource(getDeviceInfo().getLogicalAddress(), activePath,
+                        "HdmiCecLocalDeviceTv#launchRoutingControl()");
             }
         }
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 61514ab..cca73b5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -40,6 +40,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -304,6 +305,10 @@
     // Make sure HdmiCecConfig is instantiated and the XMLs are read.
     private HdmiCecConfig mHdmiCecConfig;
 
+    // Timeout value for start ARC action after an established eARC connection was terminated,
+    // e.g. because eARC was disabled in Settings.
+    private static final int EARC_TRIGGER_START_ARC_ACTION_DELAY = 500;
+
     /**
      * Interface to report send result.
      */
@@ -1641,6 +1646,13 @@
             case Constants.MESSAGE_ROUTING_CHANGE:
             case Constants.MESSAGE_SET_STREAM_PATH:
             case Constants.MESSAGE_TEXT_VIEW_ON:
+                // RequestActiveSourceAction is started after the TV finished logical address
+                // allocation. This action is used by the TV to get the active source from the CEC
+                // network. If the TV sent a source changing CEC message, this action does not have
+                // to continue anymore.
+                if (isTvDeviceEnabled()) {
+                    tv().removeAction(RequestActiveSourceAction.class);
+                }
                 sendCecCommandWithRetries(command, callback);
                 break;
             default:
@@ -4388,8 +4400,7 @@
         assertRunOnServiceThread();
         Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
         intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
-        getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
-                HdmiControlService.PERMISSION);
+        sendBroadcastAsUser(intent);
     }
 
     @ServiceThreadOnly
@@ -4398,8 +4409,17 @@
         Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
         intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
         intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra);
-        getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
-                HdmiControlService.PERMISSION);
+        sendBroadcastAsUser(intent);
+    }
+
+    // This method is used such that we can override it inside unit tests to avoid a
+    // SecurityException.
+    @ServiceThreadOnly
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+    protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+        assertRunOnServiceThread();
+        getContext().sendBroadcastAsUser(intent, UserHandle.ALL, HdmiControlService.PERMISSION);
     }
 
     @VisibleForTesting
@@ -5041,7 +5061,12 @@
             // AudioService here that the eARC connection is terminated.
             HdmiLogger.debug("eARC state change [new: HDMI_EARC_STATUS_ARC_PENDING(2)]");
             notifyEarcStatusToAudioService(false, new ArrayList<>());
-            startArcAction(true, null);
+            mHandler.postDelayed( new Runnable() {
+                @Override
+                public void run() {
+                    startArcAction(true, null);
+                }
+            }, EARC_TRIGGER_START_ARC_ACTION_DELAY);
             getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(),
                     oldEarcStatus, status, HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED);
         } else {
diff --git a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
index d250416..539a00d 100644
--- a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
@@ -21,13 +21,20 @@
 import android.util.Slog;
 
 /**
- * Feature action that sends <Request Active Source> message and waits for <Active Source>.
+ * Feature action that sends <Request Active Source> message and waits for <Active Source> on TV
+ * panels.
+ * This action has a delay before sending <Request Active Source>. This is because it should wait
+ * for a possible request from LauncherX and can be cancelled if an <Active Source> message was
+ * received or the TV switched to another input.
  */
 public class RequestActiveSourceAction extends HdmiCecFeatureAction {
     private static final String TAG = "RequestActiveSourceAction";
 
+    // State to wait for the LauncherX to call the CEC API.
+    private static final int STATE_WAIT_FOR_LAUNCHERX_API_CALL = 1;
+
     // State to wait for the <Active Source> message.
-    private static final int STATE_WAIT_FOR_ACTIVE_SOURCE = 1;
+    private static final int STATE_WAIT_FOR_ACTIVE_SOURCE = 2;
 
     // Number of retries <Request Active Source> is sent if no device answers this message.
     private static final int MAX_SEND_RETRY_COUNT = 1;
@@ -43,10 +50,12 @@
     boolean start() {
         Slog.v(TAG, "RequestActiveSourceAction started.");
 
-        sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress()));
+        mState = STATE_WAIT_FOR_LAUNCHERX_API_CALL;
 
-        mState = STATE_WAIT_FOR_ACTIVE_SOURCE;
-        addTimer(mState, HdmiConfig.TIMEOUT_MS);
+        // We wait for default timeout to allow the message triggered by the LauncherX API call to
+        // be sent by the TV and another default timeout in case the message has to be answered
+        // (e.g. TV sent a <Set Stream Path> or <Routing Change>).
+        addTimer(mState, HdmiConfig.TIMEOUT_MS * 2);
         return true;
     }
 
@@ -65,13 +74,23 @@
         if (mState != state) {
             return;
         }
-        if (mState == STATE_WAIT_FOR_ACTIVE_SOURCE) {
-            if (mSendRetryCount++ < MAX_SEND_RETRY_COUNT) {
+
+        switch (mState) {
+            case STATE_WAIT_FOR_LAUNCHERX_API_CALL:
+                mState = STATE_WAIT_FOR_ACTIVE_SOURCE;
                 sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress()));
                 addTimer(mState, HdmiConfig.TIMEOUT_MS);
-            } else {
-                finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
-            }
+                return;
+            case STATE_WAIT_FOR_ACTIVE_SOURCE:
+                if (mSendRetryCount++ < MAX_SEND_RETRY_COUNT) {
+                    sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress()));
+                    addTimer(mState, HdmiConfig.TIMEOUT_MS);
+                } else {
+                    finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
+                }
+                return;
+            default:
+                return;
         }
     }
 }
diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java
index 2703a2c0..7e18d84 100644
--- a/services/core/java/com/android/server/hdmi/SendKeyAction.java
+++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java
@@ -158,9 +158,11 @@
                 mTargetAddress, cecKeycodeAndParams), new SendMessageCallback() {
                 @Override
                 public void onSendCompleted(int error) {
-                    if (error != SendMessageResult.SUCCESS) {
+                    // Disable System Audio Mode, if the AVR doesn't acknowledge
+                    // a <User Control Pressed> message.
+                    if (error == SendMessageResult.NACK) {
                         HdmiLogger.debug(
-                            "AVR did not respond to <User Control Pressed>");
+                            "AVR did not acknowledge <User Control Pressed>");
                         localDevice().mService.setSystemAudioActivated(false);
                     }
                 }
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 4e9cf51..d32a5ed 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -84,29 +84,6 @@
             @NonNull IBinder toChannelToken);
 
     /**
-     * Sets the display id that the MouseCursorController will be forced to target. Pass
-     * {@link android.view.Display#INVALID_DISPLAY} to clear the override.
-     *
-     * Note: This method generally blocks until the pointer display override has propagated.
-     * When setting a new override, the caller should ensure that an input device that can control
-     * the mouse pointer is connected. If a new override is set when no such input device is
-     * connected, the caller may be blocked for an arbitrary period of time.
-     *
-     * @return true if the pointer displayId was set successfully, or false if it fails.
-     *
-     * @deprecated TODO(b/293587049): Not needed - remove after Pointer Icon Refactor is complete.
-     */
-    public abstract boolean setVirtualMousePointerDisplayId(int pointerDisplayId);
-
-    /**
-     * Gets the display id that the MouseCursorController is being forced to target. Returns
-     * {@link android.view.Display#INVALID_DISPLAY} if there is no override
-     *
-     * @deprecated TODO(b/293587049): Not needed - remove after Pointer Icon Refactor is complete.
-     */
-    public abstract int getVirtualMousePointerDisplayId();
-
-    /**
      * Gets the current position of the mouse cursor.
      *
      * Returns NaN-s as the coordinates if the cursor is not available.
@@ -241,4 +218,13 @@
      * display, external peripherals, fingerprint sensor, etc.
      */
     public abstract void notifyUserActivity();
+
+    /**
+     * Get the device ID of the {@link InputDevice} that used most recently.
+     *
+     * @return the last used input device ID, or
+     *     {@link android.os.IInputConstants#INVALID_INPUT_DEVICE_ID} if no device has been used
+     *     since boot.
+     */
+    public abstract int getLastUsedInputDeviceId();
 }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index eb71952..8e85b81 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -156,7 +156,6 @@
     private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
     private static final int MSG_RELOAD_DEVICE_ALIASES = 2;
     private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 3;
-    private static final int MSG_POINTER_DISPLAY_ID_CHANGED = 4;
 
     private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
     private static final AdditionalDisplayInputProperties
@@ -255,7 +254,7 @@
     // to {DisplayInfo#uniqueId} (String) so that events from the Input Device go to a
     // specific display.
     @GuardedBy("mAssociationsLock")
-    private final Map<String, String> mUniqueIdAssociations = new ArrayMap<>();
+    private final Map<String, String> mUniqueIdAssociationsByPort = new ArrayMap<>();
 
     // The associations of input devices to displays by descriptor. Maps from
     // {InputDevice#mDescriptor} to {DisplayInfo#uniqueId} (String) so that events from the
@@ -282,33 +281,9 @@
     // WARNING: Do not call other services outside of input while holding this lock.
     private final Object mAdditionalDisplayInputPropertiesLock = new Object();
 
-    // Forces the PointerController to target a specific display id.
-    @GuardedBy("mAdditionalDisplayInputPropertiesLock")
-    private int mOverriddenPointerDisplayId = Display.INVALID_DISPLAY;
-
-    // PointerController is the source of truth of the pointer display. This is the value of the
-    // latest pointer display id reported by PointerController.
-    @GuardedBy("mAdditionalDisplayInputPropertiesLock")
-    private int mAcknowledgedPointerDisplayId = Display.INVALID_DISPLAY;
-    // This is the latest display id that IMS has requested PointerController to use. If there are
-    // no devices that can control the pointer, PointerController may end up disregarding this
-    // value.
-    @GuardedBy("mAdditionalDisplayInputPropertiesLock")
-    private int mRequestedPointerDisplayId = Display.INVALID_DISPLAY;
     @GuardedBy("mAdditionalDisplayInputPropertiesLock")
     private final SparseArray<AdditionalDisplayInputProperties> mAdditionalDisplayInputProperties =
             new SparseArray<>();
-    // This contains the per-display properties that are currently applied by native code. It should
-    // be kept in sync with the properties for mRequestedPointerDisplayId.
-    @GuardedBy("mAdditionalDisplayInputPropertiesLock")
-    private final AdditionalDisplayInputProperties mCurrentDisplayProperties =
-            new AdditionalDisplayInputProperties();
-    // TODO(b/293587049): Pointer Icon Refactor: There can be more than one pointer icon
-    // visible at once. Update this to support multi-pointer use cases.
-    @GuardedBy("mAdditionalDisplayInputPropertiesLock")
-    private int mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
-    @GuardedBy("mAdditionalDisplayInputPropertiesLock")
-    private PointerIcon mPointerIcon;
 
     // Holds all the registered gesture monitors that are implemented as spy windows. The spy
     // windows are mapped by their InputChannel tokens.
@@ -617,14 +592,9 @@
         }
         mNative.setDisplayViewports(vArray);
 
-        // Attempt to update the pointer display when viewports change when there is no override.
+        // Attempt to update the default pointer display when the viewports change.
         // Take care to not make calls to window manager while holding internal locks.
-        final int pointerDisplayId = mWindowManagerCallbacks.getPointerDisplayId();
-        synchronized (mAdditionalDisplayInputPropertiesLock) {
-            if (mOverriddenPointerDisplayId == Display.INVALID_DISPLAY) {
-                updatePointerDisplayIdLocked(pointerDisplayId);
-            }
-        }
+        mNative.setPointerDisplayId(mWindowManagerCallbacks.getPointerDisplayId());
     }
 
     /**
@@ -1353,84 +1323,6 @@
                 properties -> properties.pointerIconVisible = visible);
     }
 
-    /**
-     * Update the display on which the mouse pointer is shown.
-     *
-     * @return true if the pointer displayId changed, false otherwise.
-     */
-    @GuardedBy("mAdditionalDisplayInputPropertiesLock")
-    private boolean updatePointerDisplayIdLocked(int pointerDisplayId) {
-        if (mRequestedPointerDisplayId == pointerDisplayId) {
-            return false;
-        }
-        mRequestedPointerDisplayId = pointerDisplayId;
-        mNative.setPointerDisplayId(pointerDisplayId);
-        applyAdditionalDisplayInputProperties();
-        return true;
-    }
-
-    private void handlePointerDisplayIdChanged(PointerDisplayIdChangedArgs args) {
-        synchronized (mAdditionalDisplayInputPropertiesLock) {
-            mAcknowledgedPointerDisplayId = args.mPointerDisplayId;
-            // Notify waiting threads that the display of the mouse pointer has changed.
-            mAdditionalDisplayInputPropertiesLock.notifyAll();
-        }
-        mWindowManagerCallbacks.notifyPointerDisplayIdChanged(
-                args.mPointerDisplayId, args.mXPosition, args.mYPosition);
-    }
-
-    private boolean setVirtualMousePointerDisplayIdBlocking(int overrideDisplayId) {
-        if (com.android.input.flags.Flags.enablePointerChoreographer()) {
-            throw new IllegalStateException(
-                    "This must not be used when PointerChoreographer is enabled");
-        }
-        final boolean isRemovingOverride = overrideDisplayId == Display.INVALID_DISPLAY;
-
-        // Take care to not make calls to window manager while holding internal locks.
-        final int resolvedDisplayId = isRemovingOverride
-                ? mWindowManagerCallbacks.getPointerDisplayId()
-                : overrideDisplayId;
-
-        synchronized (mAdditionalDisplayInputPropertiesLock) {
-            mOverriddenPointerDisplayId = overrideDisplayId;
-
-            if (!updatePointerDisplayIdLocked(resolvedDisplayId)
-                    && mAcknowledgedPointerDisplayId == resolvedDisplayId) {
-                // The requested pointer display is already set.
-                return true;
-            }
-            if (isRemovingOverride && mAcknowledgedPointerDisplayId == Display.INVALID_DISPLAY) {
-                // The pointer display override is being removed, but the current pointer display
-                // is already invalid. This can happen when the PointerController is destroyed as a
-                // result of the removal of all input devices that can control the pointer.
-                return true;
-            }
-            try {
-                // The pointer display changed, so wait until the change has propagated.
-                mAdditionalDisplayInputPropertiesLock.wait(5_000 /*mills*/);
-            } catch (InterruptedException ignored) {
-            }
-            // This request succeeds in two cases:
-            // - This request was to remove the override, in which case the new pointer display
-            //   could be anything that WM has set.
-            // - We are setting a new override, in which case the request only succeeds if the
-            //   reported new displayId is the one we requested. This check ensures that if two
-            //   competing overrides are requested in succession, the caller can be notified if one
-            //   of them fails.
-            return  isRemovingOverride || mAcknowledgedPointerDisplayId == overrideDisplayId;
-        }
-    }
-
-    private int getVirtualMousePointerDisplayId() {
-        if (com.android.input.flags.Flags.enablePointerChoreographer()) {
-            throw new IllegalStateException(
-                    "This must not be used when PointerChoreographer is enabled");
-        }
-        synchronized (mAdditionalDisplayInputPropertiesLock) {
-            return mOverriddenPointerDisplayId;
-        }
-    }
-
     private void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) {
         mNative.setDisplayEligibilityForPointerCapture(displayId, isEligible);
     }
@@ -1714,45 +1606,10 @@
 
     // Binder call
     @Override
-    public void setPointerIconType(int iconType) {
-        if (iconType == PointerIcon.TYPE_CUSTOM) {
-            throw new IllegalArgumentException("Use setCustomPointerIcon to set custom pointers");
-        }
-        synchronized (mAdditionalDisplayInputPropertiesLock) {
-            mPointerIcon = null;
-            mPointerIconType = iconType;
-
-            if (!mCurrentDisplayProperties.pointerIconVisible) return;
-
-            mNative.setPointerIconType(mPointerIconType);
-        }
-    }
-
-    // Binder call
-    @Override
-    public void setCustomPointerIcon(PointerIcon icon) {
-        Objects.requireNonNull(icon);
-        synchronized (mAdditionalDisplayInputPropertiesLock) {
-            mPointerIconType = PointerIcon.TYPE_CUSTOM;
-            mPointerIcon = icon;
-
-            if (!mCurrentDisplayProperties.pointerIconVisible) return;
-
-            mNative.setCustomPointerIcon(mPointerIcon);
-        }
-    }
-
-    // Binder call
-    @Override
     public boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId, int pointerId,
             IBinder inputToken) {
         Objects.requireNonNull(icon);
-        synchronized (mAdditionalDisplayInputPropertiesLock) {
-            mPointerIconType = icon.getType();
-            mPointerIcon = mPointerIconType == PointerIcon.TYPE_CUSTOM ? icon : null;
-
-            return mNative.setPointerIcon(icon, displayId, deviceId, pointerId, inputToken);
-        }
+        return mNative.setPointerIcon(icon, displayId, deviceId, pointerId, inputToken);
     }
 
     /**
@@ -1799,7 +1656,8 @@
     }
 
     @Override // Binder call
-    public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) {
+    public void addUniqueIdAssociationByPort(@NonNull String inputPort,
+            @NonNull String displayUniqueId) {
         if (!checkCallingPermission(
                 android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
                 "addUniqueIdAssociation()")) {
@@ -1810,13 +1668,13 @@
         Objects.requireNonNull(inputPort);
         Objects.requireNonNull(displayUniqueId);
         synchronized (mAssociationsLock) {
-            mUniqueIdAssociations.put(inputPort, displayUniqueId);
+            mUniqueIdAssociationsByPort.put(inputPort, displayUniqueId);
         }
         mNative.changeUniqueIdAssociation();
     }
 
     @Override // Binder call
-    public void removeUniqueIdAssociation(@NonNull String inputPort) {
+    public void removeUniqueIdAssociationByPort(@NonNull String inputPort) {
         if (!checkCallingPermission(
                 android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
                 "removeUniqueIdAssociation()")) {
@@ -1825,7 +1683,7 @@
 
         Objects.requireNonNull(inputPort);
         synchronized (mAssociationsLock) {
-            mUniqueIdAssociations.remove(inputPort);
+            mUniqueIdAssociationsByPort.remove(inputPort);
         }
         mNative.changeUniqueIdAssociation();
     }
@@ -2244,9 +2102,9 @@
                     pw.println("  display: " + v);
                 });
             }
-            if (!mUniqueIdAssociations.isEmpty()) {
+            if (!mUniqueIdAssociationsByPort.isEmpty()) {
                 pw.println("Unique Id Associations:");
-                mUniqueIdAssociations.forEach((k, v) -> {
+                mUniqueIdAssociationsByPort.forEach((k, v) -> {
                     pw.print("  port: " + k);
                     pw.println("  uniqueId: " + v);
                 });
@@ -2281,28 +2139,24 @@
 
     private void dumpDisplayInputPropertiesValues(IndentingPrintWriter pw) {
         synchronized (mAdditionalDisplayInputPropertiesLock) {
-            if (mAdditionalDisplayInputProperties.size() != 0) {
-                pw.println("mAdditionalDisplayInputProperties:");
-                pw.increaseIndent();
+            pw.println("mAdditionalDisplayInputProperties:");
+            pw.increaseIndent();
+            try {
+                if (mAdditionalDisplayInputProperties.size() == 0) {
+                    pw.println("<none>");
+                    return;
+                }
                 for (int i = 0; i < mAdditionalDisplayInputProperties.size(); i++) {
-                    pw.println("displayId: "
-                            + mAdditionalDisplayInputProperties.keyAt(i));
+                    pw.println("displayId: " + mAdditionalDisplayInputProperties.keyAt(i));
                     final AdditionalDisplayInputProperties properties =
                             mAdditionalDisplayInputProperties.valueAt(i);
                     pw.println("mousePointerAccelerationEnabled: "
                             + properties.mousePointerAccelerationEnabled);
                     pw.println("pointerIconVisible: " + properties.pointerIconVisible);
                 }
+            } finally {
                 pw.decreaseIndent();
             }
-            if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) {
-                pw.println("mOverriddenPointerDisplayId: " + mOverriddenPointerDisplayId);
-            }
-
-            pw.println("mAcknowledgedPointerDisplayId=" + mAcknowledgedPointerDisplayId);
-            pw.println("mRequestedPointerDisplayId=" + mRequestedPointerDisplayId);
-            pw.println("mPointerIconType=" + PointerIcon.typeToString(mPointerIconType));
-            pw.println("mPointerIcon=" + mPointerIcon);
         }
     }
     private boolean checkCallingPermission(String permission, String func) {
@@ -2677,10 +2531,10 @@
 
     // Native callback
     @SuppressWarnings("unused")
-    private String[] getInputUniqueIdAssociations() {
+    private String[] getInputUniqueIdAssociationsByPort() {
         final Map<String, String> associations;
         synchronized (mAssociationsLock) {
-            associations = new HashMap<>(mUniqueIdAssociations);
+            associations = new HashMap<>(mUniqueIdAssociationsByPort);
         }
 
         return flatten(associations);
@@ -2817,26 +2671,6 @@
         return null;
     }
 
-    private static class PointerDisplayIdChangedArgs {
-        final int mPointerDisplayId;
-        final float mXPosition;
-        final float mYPosition;
-        PointerDisplayIdChangedArgs(int pointerDisplayId, float xPosition, float yPosition) {
-            mPointerDisplayId = pointerDisplayId;
-            mXPosition = xPosition;
-            mYPosition = yPosition;
-        }
-    }
-
-    // Native callback.
-    @SuppressWarnings("unused")
-    @VisibleForTesting
-    void onPointerDisplayIdChanged(int pointerDisplayId, float xPosition, float yPosition) {
-        mHandler.obtainMessage(MSG_POINTER_DISPLAY_ID_CHANGED,
-                new PointerDisplayIdChangedArgs(pointerDisplayId, xPosition,
-                        yPosition)).sendToTarget();
-    }
-
     @Override
     @EnforcePermission(Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)
     public void registerStickyModifierStateListener(
@@ -2989,14 +2823,6 @@
          */
         @Nullable
         SurfaceControl createSurfaceForGestureMonitor(String name, int displayId);
-
-        /**
-         * Notify WindowManagerService when the display of the mouse pointer changes.
-         * @param displayId The display on which the mouse pointer is shown.
-         * @param x The x coordinate of the mouse pointer.
-         * @param y The y coordinate of the mouse pointer.
-         */
-        void notifyPointerDisplayIdChanged(int displayId, float x, float y);
     }
 
     /**
@@ -3040,9 +2866,6 @@
                     boolean inTabletMode = (boolean) args.arg1;
                     deliverTabletModeChanged(whenNanos, inTabletMode);
                     break;
-                case MSG_POINTER_DISPLAY_ID_CHANGED:
-                    handlePointerDisplayIdChanged((PointerDisplayIdChangedArgs) msg.obj);
-                    break;
             }
         }
     }
@@ -3267,17 +3090,6 @@
         }
 
         @Override
-        public boolean setVirtualMousePointerDisplayId(int pointerDisplayId) {
-            return InputManagerService.this
-                    .setVirtualMousePointerDisplayIdBlocking(pointerDisplayId);
-        }
-
-        @Override
-        public int getVirtualMousePointerDisplayId() {
-            return InputManagerService.this.getVirtualMousePointerDisplayId();
-        }
-
-        @Override
         public PointF getCursorPosition(int displayId) {
             final float[] p = mNative.getMouseCursorPosition(displayId);
             if (p == null || p.length != 2) {
@@ -3374,6 +3186,11 @@
         public void setStylusButtonMotionEventsEnabled(boolean enabled) {
             mNative.setStylusButtonMotionEventsEnabled(enabled);
         }
+
+        @Override
+        public int getLastUsedInputDeviceId() {
+            return mNative.getLastUsedInputDeviceId();
+        }
     }
 
     @Override
@@ -3413,44 +3230,6 @@
         }
     }
 
-    private void applyAdditionalDisplayInputProperties() {
-        synchronized (mAdditionalDisplayInputPropertiesLock) {
-            AdditionalDisplayInputProperties properties =
-                    mAdditionalDisplayInputProperties.get(mRequestedPointerDisplayId);
-            if (properties == null) properties = DEFAULT_ADDITIONAL_DISPLAY_INPUT_PROPERTIES;
-            applyAdditionalDisplayInputPropertiesLocked(properties);
-        }
-    }
-
-    @GuardedBy("mAdditionalDisplayInputPropertiesLock")
-    private void applyAdditionalDisplayInputPropertiesLocked(
-            AdditionalDisplayInputProperties properties) {
-        // Handle changes to each of the individual properties.
-        // TODO(b/293587049): This approach for updating pointer display properties is only for when
-        //  PointerChoreographer is disabled. Remove this logic when PointerChoreographer is
-        //  permanently enabled.
-
-        if (properties.pointerIconVisible != mCurrentDisplayProperties.pointerIconVisible) {
-            mCurrentDisplayProperties.pointerIconVisible = properties.pointerIconVisible;
-            if (properties.pointerIconVisible) {
-                if (mPointerIconType == PointerIcon.TYPE_CUSTOM) {
-                    Objects.requireNonNull(mPointerIcon);
-                    mNative.setCustomPointerIcon(mPointerIcon);
-                } else {
-                    mNative.setPointerIconType(mPointerIconType);
-                }
-            } else {
-                mNative.setPointerIconType(PointerIcon.TYPE_NULL);
-            }
-        }
-
-        if (properties.mousePointerAccelerationEnabled
-                != mCurrentDisplayProperties.mousePointerAccelerationEnabled) {
-            mCurrentDisplayProperties.mousePointerAccelerationEnabled =
-                    properties.mousePointerAccelerationEnabled;
-        }
-    }
-
     private void updateAdditionalDisplayInputProperties(int displayId,
             Consumer<AdditionalDisplayInputProperties> updater) {
         synchronized (mAdditionalDisplayInputPropertiesLock) {
@@ -3473,13 +3252,6 @@
             if (properties.allDefaults()) {
                 mAdditionalDisplayInputProperties.remove(displayId);
             }
-            if (displayId != mRequestedPointerDisplayId) {
-                Log.i(TAG, "Not applying additional properties for display " + displayId
-                        + " because the pointer is currently targeting display "
-                        + mRequestedPointerDisplayId + ".");
-                return;
-            }
-            applyAdditionalDisplayInputPropertiesLocked(properties);
         }
     }
 
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 32d5044..0208a32 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -189,12 +189,8 @@
 
     void disableInputDevice(int deviceId);
 
-    void setPointerIconType(int iconId);
-
     void reloadPointerIcons();
 
-    void setCustomPointerIcon(@NonNull PointerIcon icon);
-
     boolean setPointerIcon(@NonNull PointerIcon icon, int displayId, int deviceId, int pointerId,
             @NonNull IBinder inputToken);
 
@@ -275,6 +271,15 @@
 
     void setInputMethodConnectionIsActive(boolean isActive);
 
+    /**
+     * Get the device ID of the InputDevice that used most recently.
+     *
+     * @return the last used input device ID, or
+     *     {@link android.os.IInputConstants#INVALID_INPUT_DEVICE_ID} if no device has been used
+     *     since boot.
+     */
+    int getLastUsedInputDeviceId();
+
     /** The native implementation of InputManagerService methods. */
     class NativeImpl implements NativeInputManagerService {
         /** Pointer to native input manager service object, used by native code. */
@@ -467,15 +472,9 @@
         public native void disableInputDevice(int deviceId);
 
         @Override
-        public native void setPointerIconType(int iconId);
-
-        @Override
         public native void reloadPointerIcons();
 
         @Override
-        public native void setCustomPointerIcon(PointerIcon icon);
-
-        @Override
         public native boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId,
                 int pointerId, IBinder inputToken);
 
@@ -554,5 +553,8 @@
 
         @Override
         public native void setInputMethodConnectionIsActive(boolean isActive);
+
+        @Override
+        public native int getLastUsedInputDeviceId();
     }
 }
diff --git a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
index 6eae9a4..d7d57df 100644
--- a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
+++ b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
@@ -240,7 +240,8 @@
             return;
         }
 
-        post(() -> handleRotaryInput(MotionEvent.obtain((MotionEvent) event)));
+        MotionEvent motionEvent = MotionEvent.obtain(event);
+        post(() -> handleRotaryInput(motionEvent));
     }
 
     private void handleKeyEvent(KeyEvent keyEvent) {
diff --git a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java
index 035a748..ad98b4a 100644
--- a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java
+++ b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java
@@ -29,6 +29,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
 import com.android.internal.inputmethod.IInlineSuggestionsResponseCallback;
+import com.android.internal.inputmethod.InlineSuggestionsRequestCallback;
 import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
 
 /**
@@ -40,14 +41,21 @@
 
     @NonNull private final InputMethodManagerService mService;
 
+    /**
+     * The host input token of the input method that is currently associated with this controller.
+     */
+    @GuardedBy("ImfLock.class")
+    @Nullable
+    private IBinder mCurHostInputToken;
+
     private static final class CreateInlineSuggestionsRequest {
         @NonNull final InlineSuggestionsRequestInfo mRequestInfo;
-        @NonNull final IInlineSuggestionsRequestCallback mCallback;
+        @NonNull final InlineSuggestionsRequestCallback mCallback;
         @NonNull final String mPackageName;
 
         CreateInlineSuggestionsRequest(
                 @NonNull InlineSuggestionsRequestInfo requestInfo,
-                @NonNull IInlineSuggestionsRequestCallback callback,
+                @NonNull InlineSuggestionsRequestCallback callback,
                 @NonNull String packageName) {
             mRequestInfo = requestInfo;
             mCallback = callback;
@@ -71,41 +79,49 @@
      */
     @GuardedBy("ImfLock.class")
     @Nullable
-    private IInlineSuggestionsRequestCallback mInlineSuggestionsRequestCallback;
+    private InlineSuggestionsRequestCallback mInlineSuggestionsRequestCallback;
 
     AutofillSuggestionsController(@NonNull InputMethodManagerService service) {
         mService = service;
     }
 
     @GuardedBy("ImfLock.class")
+    void onResetSystemUi() {
+        mCurHostInputToken = null;
+    }
+
+    @Nullable
+    @GuardedBy("ImfLock.class")
+    IBinder getCurHostInputToken() {
+        return mCurHostInputToken;
+    }
+
+    @GuardedBy("ImfLock.class")
     void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
-            InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback,
+            InlineSuggestionsRequestInfo requestInfo, InlineSuggestionsRequestCallback callback,
             boolean touchExplorationEnabled) {
         clearPendingInlineSuggestionsRequest();
         mInlineSuggestionsRequestCallback = callback;
         final InputMethodInfo imi = mService.queryInputMethodForCurrentUserLocked(
                 mService.getSelectedMethodIdLocked());
-        try {
-            if (userId == mService.getCurrentImeUserIdLocked()
-                    && imi != null && isInlineSuggestionsEnabled(imi, touchExplorationEnabled)) {
-                mPendingInlineSuggestionsRequest = new CreateInlineSuggestionsRequest(
-                        requestInfo, callback, imi.getPackageName());
-                if (mService.getCurMethodLocked() != null) {
-                    // In the normal case when the IME is connected, we can make the request here.
-                    performOnCreateInlineSuggestionsRequest();
-                } else {
-                    // Otherwise, the next time the IME connection is established,
-                    // InputMethodBindingController.mMainConnection#onServiceConnected() will call
-                    // into #performOnCreateInlineSuggestionsRequestLocked() to make the request.
-                    if (DEBUG) {
-                        Slog.d(TAG, "IME not connected. Delaying inline suggestions request.");
-                    }
-                }
+
+        if (userId == mService.getCurrentImeUserIdLocked()
+                && imi != null && isInlineSuggestionsEnabled(imi, touchExplorationEnabled)) {
+            mPendingInlineSuggestionsRequest = new CreateInlineSuggestionsRequest(
+                    requestInfo, callback, imi.getPackageName());
+            if (mService.getCurMethodLocked() != null) {
+                // In the normal case when the IME is connected, we can make the request here.
+                performOnCreateInlineSuggestionsRequest();
             } else {
-                callback.onInlineSuggestionsUnsupported();
+                // Otherwise, the next time the IME connection is established,
+                // InputMethodBindingController.mMainConnection#onServiceConnected() will call
+                // into #performOnCreateInlineSuggestionsRequestLocked() to make the request.
+                if (DEBUG) {
+                    Slog.d(TAG, "IME not connected. Delaying inline suggestions request.");
+                }
             }
-        } catch (RemoteException e) {
-            Slog.w(TAG, "RemoteException calling onCreateInlineSuggestionsRequest(): " + e);
+        } else {
+            callback.onInlineSuggestionsUnsupported();
         }
     }
 
@@ -124,8 +140,7 @@
                             mPendingInlineSuggestionsRequest.mCallback,
                             mPendingInlineSuggestionsRequest.mPackageName,
                             mService.getCurTokenDisplayIdLocked(),
-                            mService.getCurTokenLocked(),
-                            mService);
+                            mService.getCurTokenLocked());
             curMethod.onCreateInlineSuggestionsRequest(
                     mPendingInlineSuggestionsRequest.mRequestInfo, callback);
         } else {
@@ -149,11 +164,7 @@
     @GuardedBy("ImfLock.class")
     void invalidateAutofillSession() {
         if (mInlineSuggestionsRequestCallback != null) {
-            try {
-                mInlineSuggestionsRequestCallback.onInlineSuggestionsSessionInvalidated();
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Cannot invalidate autofill session.", e);
-            }
+            mInlineSuggestionsRequestCallback.onInlineSuggestionsSessionInvalidated();
         }
     }
 
@@ -161,26 +172,24 @@
      * The decorator which validates the host package name in the
      * {@link InlineSuggestionsRequest} argument to make sure it matches the IME package name.
      */
-    private static final class InlineSuggestionsRequestCallbackDecorator
+    private final class InlineSuggestionsRequestCallbackDecorator
             extends IInlineSuggestionsRequestCallback.Stub {
-        @NonNull private final IInlineSuggestionsRequestCallback mCallback;
+        @NonNull private final InlineSuggestionsRequestCallback mCallback;
         @NonNull private final String mImePackageName;
         private final int mImeDisplayId;
         @NonNull private final IBinder mImeToken;
-        @NonNull private final InputMethodManagerService mImms;
 
         InlineSuggestionsRequestCallbackDecorator(
-                @NonNull IInlineSuggestionsRequestCallback callback, @NonNull String imePackageName,
-                int displayId, @NonNull IBinder imeToken, @NonNull InputMethodManagerService imms) {
+                @NonNull InlineSuggestionsRequestCallback callback, @NonNull String imePackageName,
+                int displayId, @NonNull IBinder imeToken) {
             mCallback = callback;
             mImePackageName = imePackageName;
             mImeDisplayId = displayId;
             mImeToken = imeToken;
-            mImms = imms;
         }
 
         @Override
-        public void onInlineSuggestionsUnsupported() throws RemoteException {
+        public void onInlineSuggestionsUnsupported() {
             mCallback.onInlineSuggestionsUnsupported();
         }
 
@@ -195,37 +204,42 @@
                                 + "].");
             }
             request.setHostDisplayId(mImeDisplayId);
-            mImms.setCurHostInputToken(mImeToken, request.getHostInputToken());
+            synchronized (ImfLock.class) {
+                final IBinder curImeToken = mService.getCurTokenLocked();
+                if (mImeToken == curImeToken) {
+                    mCurHostInputToken = request.getHostInputToken();
+                }
+            }
             mCallback.onInlineSuggestionsRequest(request, callback);
         }
 
         @Override
-        public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException {
+        public void onInputMethodStartInput(AutofillId imeFieldId) {
             mCallback.onInputMethodStartInput(imeFieldId);
         }
 
         @Override
-        public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException {
+        public void onInputMethodShowInputRequested(boolean requestResult) {
             mCallback.onInputMethodShowInputRequested(requestResult);
         }
 
         @Override
-        public void onInputMethodStartInputView() throws RemoteException {
+        public void onInputMethodStartInputView() {
             mCallback.onInputMethodStartInputView();
         }
 
         @Override
-        public void onInputMethodFinishInputView() throws RemoteException {
+        public void onInputMethodFinishInputView() {
             mCallback.onInputMethodFinishInputView();
         }
 
         @Override
-        public void onInputMethodFinishInput() throws RemoteException {
+        public void onInputMethodFinishInput() {
             mCallback.onInputMethodFinishInput();
         }
 
         @Override
-        public void onInlineSuggestionsSessionInvalidated() throws RemoteException {
+        public void onInlineSuggestionsSessionInvalidated() {
             mCallback.onInlineSuggestionsSessionInvalidated();
         }
     }
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index 7956e03..79f1a9c 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -330,14 +330,10 @@
         mHandwritingSurface.startIntercepting(imePid, imeUid);
 
         // Unset the pointer icon for the stylus in case the app had set it.
-        if (com.android.input.flags.Flags.enablePointerChoreographer()) {
-            Objects.requireNonNull(mContext.getSystemService(InputManager.class)).setPointerIcon(
-                    PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED),
-                    downEvent.getDisplayId(), downEvent.getDeviceId(), downEvent.getPointerId(0),
-                    mHandwritingSurface.getInputChannel().getToken());
-        } else {
-            InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED);
-        }
+        Objects.requireNonNull(mContext.getSystemService(InputManager.class)).setPointerIcon(
+                PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED),
+                downEvent.getDisplayId(), downEvent.getDeviceId(), downEvent.getPointerId(0),
+                mHandwritingSurface.getInputChannel().getToken());
 
         return new HandwritingSession(mCurrentRequestId, mHandwritingSurface.getInputChannel(),
                 mHandwritingBuffer);
diff --git a/services/core/java/com/android/server/inputmethod/ImeBindingState.java b/services/core/java/com/android/server/inputmethod/ImeBindingState.java
index 4c20c3b..f78ea84 100644
--- a/services/core/java/com/android/server/inputmethod/ImeBindingState.java
+++ b/services/core/java/com/android/server/inputmethod/ImeBindingState.java
@@ -21,7 +21,9 @@
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
 
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.os.IBinder;
+import android.os.UserHandle;
 import android.util.Printer;
 import android.util.proto.ProtoOutputStream;
 import android.view.WindowManager;
@@ -36,6 +38,9 @@
  */
 final class ImeBindingState {
 
+    @UserIdInt
+    final int mUserId;
+
     /**
      * The last window token that we confirmed to be focused.  This is always updated upon
      * reports from the input method client. If the window state is already changed before the
@@ -90,6 +95,7 @@
 
     static ImeBindingState newEmptyState() {
         return new ImeBindingState(
+                /*userId=*/ UserHandle.USER_NULL,
                 /*focusedWindow=*/ null,
                 /*focusedWindowSoftInputMode=*/ SOFT_INPUT_STATE_UNSPECIFIED,
                 /*focusedWindowClient=*/ null,
@@ -97,10 +103,12 @@
         );
     }
 
-    ImeBindingState(@Nullable IBinder focusedWindow,
+    ImeBindingState(@UserIdInt int userId,
+            @Nullable IBinder focusedWindow,
             @SoftInputModeFlags int focusedWindowSoftInputMode,
             @Nullable ClientState focusedWindowClient,
             @Nullable EditorInfo focusedWindowEditorInfo) {
+        mUserId = userId;
         mFocusedWindow = focusedWindow;
         mFocusedWindowSoftInputMode = focusedWindowSoftInputMode;
         mFocusedWindowClient = focusedWindowClient;
diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
index 1c14fc1..fff0e6e 100644
--- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -133,6 +133,13 @@
         }
     }
 
+    @Override
+    public void onDispatched(@NonNull ImeTracker.Token statsToken) {
+        synchronized (mLock) {
+            mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+        }
+    }
+
     /**
      * Updates the IME request tracking token with new information available in IMMS.
      *
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index b709174..ad99950 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -39,6 +39,7 @@
 import android.provider.Settings;
 import android.util.EventLog;
 import android.util.Slog;
+import android.view.Display;
 import android.view.WindowManager;
 import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodInfo;
@@ -78,6 +79,7 @@
     @GuardedBy("ImfLock.class") @Nullable private IInputMethodInvoker mCurMethod;
     @GuardedBy("ImfLock.class") private int mCurMethodUid = Process.INVALID_UID;
     @GuardedBy("ImfLock.class") @Nullable private IBinder mCurToken;
+    @GuardedBy("ImfLock.class") private int mCurTokenDisplayId = Display.INVALID_DISPLAY;
     @GuardedBy("ImfLock.class") private int mCurSeq;
     @GuardedBy("ImfLock.class") private boolean mVisibleBound;
     @GuardedBy("ImfLock.class") private boolean mSupportsStylusHw;
@@ -193,6 +195,17 @@
     }
 
     /**
+     * Returns the displayId associated with {@link #getCurToken()}.
+     *
+     * @return the displayId associated with {@link #getCurToken()}. {@link Display#INVALID_DISPLAY}
+     *         while {@link #getCurToken()} returns {@code null}
+     */
+    @GuardedBy("ImfLock.class")
+    int getCurTokenDisplayId() {
+        return mCurTokenDisplayId;
+    }
+
+    /**
      * The Intent used to connect to the current input method.
      */
     @GuardedBy("ImfLock.class")
@@ -412,15 +425,14 @@
 
     @GuardedBy("ImfLock.class")
     private void removeCurrentToken() {
-        int curTokenDisplayId = mService.getCurTokenDisplayIdLocked();
-
         if (DEBUG) {
             Slog.v(TAG,
-                    "Removing window token: " + mCurToken + " for display: " + curTokenDisplayId);
+                    "Removing window token: " + mCurToken + " for display: " + mCurTokenDisplayId);
         }
         mWindowManagerInternal.removeWindowToken(mCurToken, true /* removeWindows */,
-                false /* animateExit */, curTokenDisplayId);
+                false /* animateExit */, mCurTokenDisplayId);
         mCurToken = null;
+        mCurTokenDisplayId = Display.INVALID_DISPLAY;
     }
 
     @GuardedBy("ImfLock.class")
@@ -443,7 +455,16 @@
             mCurId = info.getId();
             mLastBindTime = SystemClock.uptimeMillis();
 
-            addFreshWindowToken();
+            final int displayIdToShowIme = mService.getDisplayIdToShowImeLocked();
+            mCurToken = new Binder();
+            mCurTokenDisplayId = displayIdToShowIme;
+            if (DEBUG) {
+                Slog.v(TAG, "Adding window token: " + mCurToken + " for display: "
+                        + displayIdToShowIme);
+            }
+            mWindowManagerInternal.addWindowToken(mCurToken,
+                    WindowManager.LayoutParams.TYPE_INPUT_METHOD,
+                    displayIdToShowIme, null /* options */);
             return new InputBindResult(
                     InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
                     null, null, null, mCurId, mCurSeq, false);
@@ -471,22 +492,6 @@
     }
 
     @GuardedBy("ImfLock.class")
-    private void addFreshWindowToken() {
-        int displayIdToShowIme = mService.getDisplayIdToShowImeLocked();
-        mCurToken = new Binder();
-
-        mService.setCurTokenDisplayIdLocked(displayIdToShowIme);
-
-        if (DEBUG) {
-            Slog.v(TAG, "Adding window token: " + mCurToken + " for display: "
-                    + displayIdToShowIme);
-        }
-        mWindowManagerInternal.addWindowToken(mCurToken,
-                WindowManager.LayoutParams.TYPE_INPUT_METHOD,
-                displayIdToShowIme, null /* options */);
-    }
-
-    @GuardedBy("ImfLock.class")
     private void unbindMainConnection() {
         mContext.unbindService(mMainConnection);
         mHasMainConnection = false;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
index 6339686..458c359 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.os.Parcel;
 import android.text.TextUtils;
 import android.util.Slog;
 import android.view.inputmethod.InputMethodInfo;
@@ -323,4 +324,24 @@
         return SubtypeUtils.containsSubtypeOf(imi, requiredLocale, checkCountry,
                 requiredSubtypeMode);
     }
+
+    /**
+     * Marshals the given {@link InputMethodInfo} into a byte array.
+     *
+     * @param imi {@link InputMethodInfo} to be marshalled
+     * @return a byte array where the given {@link InputMethodInfo} is marshalled
+     */
+    @NonNull
+    static byte[] marshal(@NonNull InputMethodInfo imi) {
+        Parcel parcel = null;
+        try {
+            parcel = Parcel.obtain();
+            parcel.writeTypedObject(imi, 0);
+            return parcel.marshall();
+        } finally {
+            if (parcel != null) {
+                parcel.recycle();
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 1d048cb..dace67f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -25,7 +25,7 @@
 import android.view.inputmethod.InputMethodInfo;
 
 import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
-import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
+import com.android.internal.inputmethod.InlineSuggestionsRequestCallback;
 import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.server.LocalServices;
@@ -86,11 +86,11 @@
      *
      * @param userId      the user ID to be queried
      * @param requestInfo information needed to create an {@link InlineSuggestionsRequest}.
-     * @param cb          {@link IInlineSuggestionsRequestCallback} used to pass back the request
+     * @param cb          {@link InlineSuggestionsRequestCallback} used to pass back the request
      *                    object
      */
     public abstract void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
-            InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb);
+            InlineSuggestionsRequestInfo requestInfo, InlineSuggestionsRequestCallback cb);
 
     /**
      * Force switch to the enabled input method by {@code imeId} for current user. If the input
@@ -150,10 +150,11 @@
      *
      * @param sourceInputToken the source token.
      * @param displayId        the display hosting the IME window
+     * @param userId           the user ID this request is about
      * @return {@code true} if the transfer is successful
      */
     public abstract boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
-            int displayId);
+            int displayId, @UserIdInt int userId);
 
     /**
      * Reports that IME control has transferred to the given window token, or if null that
@@ -262,7 +263,7 @@
                 @Override
                 public void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
                         InlineSuggestionsRequestInfo requestInfo,
-                        IInlineSuggestionsRequestCallback cb) {
+                        InlineSuggestionsRequestCallback cb) {
                 }
 
                 @Override
@@ -287,7 +288,7 @@
 
                 @Override
                 public boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
-                        int displayId) {
+                        int displayId, @UserIdInt int userId) {
                     return false;
                 }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 14d04119..54bf1f3 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -147,7 +147,6 @@
 import com.android.internal.inputmethod.IBooleanListener;
 import com.android.internal.inputmethod.IConnectionlessHandwritingCallback;
 import com.android.internal.inputmethod.IImeTracker;
-import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
 import com.android.internal.inputmethod.IInputContentUriToken;
 import com.android.internal.inputmethod.IInputMethod;
 import com.android.internal.inputmethod.IInputMethodClient;
@@ -157,6 +156,7 @@
 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
 import com.android.internal.inputmethod.IRemoteInputConnection;
 import com.android.internal.inputmethod.ImeTracing;
+import com.android.internal.inputmethod.InlineSuggestionsRequestCallback;
 import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
 import com.android.internal.inputmethod.InputBindResult;
 import com.android.internal.inputmethod.InputMethodDebug;
@@ -262,6 +262,7 @@
     private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
     private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
     private static final String HANDLER_THREAD_NAME = "android.imms";
+    private static final String PACKAGE_MONITOR_THREAD_NAME = "android.imms2";
 
     /**
      * When set, {@link #startInputUncheckedLocked} will return
@@ -281,10 +282,35 @@
     @NonNull
     private final String[] mNonPreemptibleInputMethods;
 
+    /**
+     * See {@link #shouldEnableExperimentalConcurrentMultiUserMode(Context)} about when set to be
+     * {@code true}.
+     */
+    private final boolean mExperimentalConcurrentMultiUserModeEnabled;
+
+    /**
+     * Returns {@code true} if experimental concurrent multi-user mode is enabled.
+     *
+     * <p>Currently not compatible with profiles (e.g. work profile).</p>
+     *
+     * @param context {@link Context} to be used to query
+     *                {@link PackageManager#FEATURE_AUTOMOTIVE}
+     * @return {@code true} if experimental concurrent multi-user mode is enabled.
+     */
+    static boolean shouldEnableExperimentalConcurrentMultiUserMode(@NonNull Context context) {
+        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+                && UserManager.isVisibleBackgroundUsersEnabled()
+                && context.getResources().getBoolean(android.R.bool.config_perDisplayFocusEnabled)
+                && Flags.concurrentInputMethods();
+    }
+
     final Context mContext;
     final Resources mRes;
     private final Handler mHandler;
 
+    @NonNull
+    private final Handler mPackageMonitorHandler;
+
     @MultiUserUnawareField
     @UserIdInt
     @GuardedBy("ImfLock.class")
@@ -307,14 +333,17 @@
     @MultiUserUnawareField
     private final InputMethodMenuController mMenuController;
     @MultiUserUnawareField
-    @NonNull private final AutofillSuggestionsController mAutofillController;
+    @NonNull
+    private final AutofillSuggestionsController mAutofillController;
 
     @GuardedBy("ImfLock.class")
     @MultiUserUnawareField
-    @NonNull private final ImeVisibilityStateComputer mVisibilityStateComputer;
+    @NonNull
+    private final ImeVisibilityStateComputer mVisibilityStateComputer;
 
     @GuardedBy("ImfLock.class")
-    @NonNull private final DefaultImeVisibilityApplier mVisibilityApplier;
+    @NonNull
+    private final DefaultImeVisibilityApplier mVisibilityApplier;
 
     /**
      * Cache the result of {@code LocalServices.getService(AudioManagerInternal.class)}.
@@ -363,7 +392,8 @@
     @MultiUserUnawareField
     private int mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
 
-    @Nullable private StatusBarManagerInternal mStatusBarManagerInternal;
+    @Nullable
+    private StatusBarManagerInternal mStatusBarManagerInternal;
     private boolean mShowOngoingImeSwitcherForPhones;
     @GuardedBy("ImfLock.class")
     @MultiUserUnawareField
@@ -481,26 +511,6 @@
         return userData.mBindingController.getSelectedMethodId();
     }
 
-    /**
-     * The current binding sequence number, incremented every time there is
-     * a new bind performed.
-     */
-    @GuardedBy("ImfLock.class")
-    private int getSequenceNumberLocked() {
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        return userData.mBindingController.getSequenceNumber();
-    }
-
-    /**
-     * Increase the current binding sequence number by one.
-     * Reset to 1 on overflow.
-     */
-    @GuardedBy("ImfLock.class")
-    private void advanceSequenceNumberLocked() {
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        userData.mBindingController.advanceSequenceNumber();
-    }
-
     @GuardedBy("ImfLock.class")
     @Nullable
     InputMethodInfo queryInputMethodForCurrentUserLocked(@NonNull String imeId) {
@@ -538,7 +548,8 @@
      * The {@link IRemoteAccessibilityInputConnection} last provided by the current client.
      */
     @MultiUserUnawareField
-    @Nullable IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection;
+    @Nullable
+    IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection;
 
     /**
      * The {@link EditorInfo} last provided by the current client.
@@ -548,21 +559,6 @@
     EditorInfo mCurEditorInfo;
 
     /**
-     * Id obtained with {@link InputMethodInfo#getId()} for the input method that we are currently
-     * connected to or in the process of connecting to.
-     *
-     * <p>This can be {@code null} when no input method is connected.</p>
-     *
-     * @see #getSelectedMethodIdLocked()
-     */
-    @GuardedBy("ImfLock.class")
-    @Nullable
-    private String getCurIdLocked() {
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        return userData.mBindingController.getCurId();
-    }
-
-    /**
      * The current subtype of the current input method.
      */
     @MultiUserUnawareField
@@ -578,16 +574,6 @@
     private final WeakHashMap<IBinder, Boolean> mFocusedWindowPerceptible = new WeakHashMap<>();
 
     /**
-     * Set to true if our ServiceConnection is currently actively bound to
-     * a service (whether or not we have gotten its IBinder back yet).
-     */
-    @GuardedBy("ImfLock.class")
-    private boolean hasConnectionLocked() {
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        return userData.mBindingController.hasMainConnection();
-    }
-
-    /**
      * The token tracking the current IME show request that is waiting for a connection to an IME,
      * otherwise {@code null}.
      */
@@ -602,16 +588,6 @@
     boolean mInFullscreenMode;
 
     /**
-     * The Intent used to connect to the current input method.
-     */
-    @GuardedBy("ImfLock.class")
-    @Nullable
-    private Intent getCurIntentLocked() {
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        return userData.mBindingController.getCurIntent();
-    }
-
-    /**
      * The token we have made for the currently active input method, to
      * identify it in the future.
      */
@@ -627,26 +603,10 @@
      */
     @GuardedBy("ImfLock.class")
     int getCurTokenDisplayIdLocked() {
-        return mCurTokenDisplayId;
+        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+        return userData.mBindingController.getCurTokenDisplayId();
     }
 
-    @GuardedBy("ImfLock.class")
-    void setCurTokenDisplayIdLocked(int curTokenDisplayId) {
-        mCurTokenDisplayId = curTokenDisplayId;
-    }
-
-    @GuardedBy("ImfLock.class")
-    @MultiUserUnawareField
-    private int mCurTokenDisplayId = INVALID_DISPLAY;
-
-    /**
-     * The host input token of the current active input method.
-     */
-    @GuardedBy("ImfLock.class")
-    @Nullable
-    @MultiUserUnawareField
-    private IBinder mCurHostInputToken;
-
     /**
      * The display ID of the input method indicates the fallback display which returned by
      * {@link #computeImeDisplayIdForTarget}.
@@ -665,25 +625,6 @@
     }
 
     /**
-     * If not {@link Process#INVALID_UID}, then the UID of {@link #getCurIntentLocked()}.
-     */
-    @GuardedBy("ImfLock.class")
-    private int getCurMethodUidLocked() {
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        return userData.mBindingController.getCurMethodUid();
-    }
-
-    /**
-     * Time that we last initiated a bind to the input method, to determine
-     * if we should try to disconnect and reconnect to it.
-     */
-    @GuardedBy("ImfLock.class")
-    private long getLastBindTimeLocked() {
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        return userData.mBindingController.getLastBindTime();
-    }
-
-    /**
      * Have we called mCurMethod.bindInput()?
      */
     @MultiUserUnawareField
@@ -804,7 +745,8 @@
             mRegistered = true;
         }
 
-        @Override public void onChange(boolean selfChange, Uri uri) {
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
             final Uri showImeUri = Settings.Secure.getUriFor(
                     Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
             final Uri accessibilityRequestingNoImeUri = Settings.Secure.getUriFor(
@@ -897,10 +839,10 @@
             }
             for (int userId : mUserManagerInternal.getUserIds()) {
                 final InputMethodSettings settings = queryInputMethodServicesInternal(
-                                mContext,
-                                userId,
-                                AdditionalSubtypeMapRepository.get(userId),
-                                DirectBootAwareness.AUTO);
+                        mContext,
+                        userId,
+                        AdditionalSubtypeMapRepository.get(userId),
+                        DirectBootAwareness.AUTO);
                 InputMethodSettingsRepository.put(userId, settings);
             }
             postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */);
@@ -912,37 +854,6 @@
 
     final class MyPackageMonitor extends PackageMonitor {
         /**
-         * Package names that are known to contain {@link InputMethodService}.
-         *
-         * <p>No need to include packages because of direct-boot unaware IMEs since we always rescan
-         * all the packages when the user is unlocked, and direct-boot awareness will not be changed
-         * dynamically unless the entire package is updated, which also always triggers package
-         * rescanning.</p>
-         */
-        @GuardedBy("ImfLock.class")
-        private final ArraySet<String> mKnownImePackageNames = new ArraySet<>();
-
-        /**
-         * Packages that are appeared, disappeared, or modified for whatever reason.
-         *
-         * <p>Note: For now we intentionally use {@link ArrayList} instead of {@link ArraySet}
-         * because 1) the number of elements is almost always 1 or so, and 2) we do not care
-         * duplicate elements for our use case.</p>
-         *
-         * <p>This object must be accessed only from callback methods in {@link PackageMonitor},
-         * which should be bound to {@link #getRegisteredHandler()}.</p>
-         */
-        private final ArrayList<String> mChangedPackages = new ArrayList<>();
-
-        /**
-         * {@code true} if one or more packages that contain {@link InputMethodService} appeared.
-         *
-         * <p>This field must be accessed only from callback methods in {@link PackageMonitor},
-         * which should be bound to {@link #getRegisteredHandler()}.</p>
-         */
-        private boolean mImePackageAppeared = false;
-
-        /**
          * Remembers package names passed to {@link #onPackageDataCleared(String, int)}.
          *
          * <p>This field must be accessed only from callback methods in {@link PackageMonitor},
@@ -955,16 +866,6 @@
         }
 
         @GuardedBy("ImfLock.class")
-        void clearKnownImePackageNamesLocked() {
-            mKnownImePackageNames.clear();
-        }
-
-        @GuardedBy("ImfLock.class")
-        void addKnownImePackageNameLocked(@NonNull String packageName) {
-            mKnownImePackageNames.add(packageName);
-        }
-
-        @GuardedBy("ImfLock.class")
         private boolean isChangingPackagesOfCurrentUserLocked() {
             final int userId = getChangingUserId();
             final boolean retval = userId == mCurrentUserId;
@@ -1014,52 +915,7 @@
         }
 
         @Override
-        public void onPackageAppeared(String packageName, int reason) {
-            if (!mImePackageAppeared) {
-                final PackageManager pm = mContext.getPackageManager();
-                final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
-                        new Intent(InputMethod.SERVICE_INTERFACE).setPackage(packageName),
-                        PackageManager.MATCH_DISABLED_COMPONENTS, getChangingUserId());
-                // No need to lock this because we access it only on getRegisteredHandler().
-                if (!services.isEmpty()) {
-                    mImePackageAppeared = true;
-                }
-            }
-            // No need to lock this because we access it only on getRegisteredHandler().
-            mChangedPackages.add(packageName);
-        }
-
-        @Override
-        public void onPackageDisappeared(String packageName, int reason) {
-            // No need to lock this because we access it only on getRegisteredHandler().
-            mChangedPackages.add(packageName);
-        }
-
-        @Override
-        public void onPackageModified(String packageName) {
-            // No need to lock this because we access it only on getRegisteredHandler().
-            mChangedPackages.add(packageName);
-        }
-
-        @Override
-        public void onPackagesSuspended(String[] packages) {
-            // No need to lock this because we access it only on getRegisteredHandler().
-            for (String packageName : packages) {
-                mChangedPackages.add(packageName);
-            }
-        }
-
-        @Override
-        public void onPackagesUnsuspended(String[] packages) {
-            // No need to lock this because we access it only on getRegisteredHandler().
-            for (String packageName : packages) {
-                mChangedPackages.add(packageName);
-            }
-        }
-
-        @Override
         public void onPackageDataCleared(String packageName, int uid) {
-            mChangedPackages.add(packageName);
             mDataClearedPackages.add(packageName);
         }
 
@@ -1071,37 +927,20 @@
 
         private void clearPackageChangeState() {
             // No need to lock them because we access these fields only on getRegisteredHandler().
-            mChangedPackages.clear();
             mDataClearedPackages.clear();
-            mImePackageAppeared = false;
-        }
-
-        @GuardedBy("ImfLock.class")
-        private boolean shouldRebuildInputMethodListLocked() {
-            // This method is guaranteed to be called only by getRegisteredHandler().
-
-            // If there is any new package that contains at least one IME, then rebuilt the list
-            // of IMEs.
-            if (mImePackageAppeared) {
-                return true;
-            }
-
-            // Otherwise, check if mKnownImePackageNames and mChangedPackages have any intersection.
-            // TODO: Consider to create a utility method to do the following test. List.retainAll()
-            // is an option, but it may still do some extra operations that we do not need here.
-            final int numPackages = mChangedPackages.size();
-            for (int i = 0; i < numPackages; ++i) {
-                final String packageName = mChangedPackages.get(i);
-                if (mKnownImePackageNames.contains(packageName)) {
-                    return true;
-                }
-            }
-            return false;
         }
 
         private void onFinishPackageChangesInternal() {
+            final int userId = getChangingUserId();
+
+            // Instantiating InputMethodInfo requires disk I/O.
+            // Do them before acquiring the lock to minimize the chances of ANR (b/340221861).
+            final var newMethodMapWithoutAdditionalSubtypes =
+                    queryInputMethodServicesInternal(mContext, userId,
+                            AdditionalSubtypeMap.EMPTY_MAP, DirectBootAwareness.AUTO)
+                            .getMethodMap();
+
             synchronized (ImfLock.class) {
-                final int userId = getChangingUserId();
                 final boolean isCurrentUser = (userId == mCurrentUserId);
                 final AdditionalSubtypeMap additionalSubtypeMap =
                         AdditionalSubtypeMapRepository.get(userId);
@@ -1149,13 +988,17 @@
                     AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
                             settings.getMethodMap());
                 }
-                if (isCurrentUser
-                        && !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
+
+                final var newMethodMap = newMethodMapWithoutAdditionalSubtypes
+                        .applyAdditionalSubtypes(newAdditionalSubtypeMap);
+
+                if (InputMethodMap.areSame(settings.getMethodMap(), newMethodMap)) {
+                    // No update in the actual IME map.
                     return;
                 }
 
-                final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
-                        userId, newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
+                final InputMethodSettings newSettings =
+                        InputMethodSettings.create(newMethodMap, userId);
                 InputMethodSettingsRepository.put(userId, newSettings);
                 if (!isCurrentUser) {
                     return;
@@ -1254,8 +1097,10 @@
     public static final class Lifecycle extends SystemService {
         private final InputMethodManagerService mService;
 
+
         public Lifecycle(Context context) {
-            this(context, new InputMethodManagerService(context));
+            this(context, new InputMethodManagerService(context,
+                            shouldEnableExperimentalConcurrentMultiUserMode(context)));
         }
 
         public Lifecycle(
@@ -1307,9 +1152,15 @@
         @Override
         public void onUserStarting(TargetUser user) {
             // Called on ActivityManager thread.
-            SecureSettingsWrapper.onUserStarting(user.getUserIdentifier());
+            final int userId = user.getUserIdentifier();
+            SecureSettingsWrapper.onUserStarting(userId);
             synchronized (ImfLock.class) {
-                mService.mUserDataRepository.getOrCreate(user.getUserIdentifier());
+                mService.mUserDataRepository.getOrCreate(userId);
+                if (mService.mExperimentalConcurrentMultiUserModeEnabled) {
+                    if (mService.mCurrentUserId != userId) {
+                        mService.experimentalInitializeVisibleBackgroundUserLocked(userId);
+                    }
+                }
             }
         }
 
@@ -1330,6 +1181,8 @@
                 // We need to rebuild IMEs.
                 postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
                 updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
+            } else if (mExperimentalConcurrentMultiUserModeEnabled) {
+                experimentalInitializeVisibleBackgroundUserLocked(userId);
             }
         }
     }
@@ -1354,16 +1207,21 @@
         mHandler.post(task);
     }
 
-    public InputMethodManagerService(Context context) {
-        this(context, null, null);
+    public InputMethodManagerService(Context context,
+            boolean experimentalConcurrentMultiUserModeEnabled) {
+        this(context, experimentalConcurrentMultiUserModeEnabled, null, null, null);
     }
 
     @VisibleForTesting
     InputMethodManagerService(
             Context context,
+            boolean experimentalConcurrentMultiUserModeEnabled,
             @Nullable ServiceThread serviceThreadForTesting,
+            @Nullable ServiceThread packageMonitorThreadForTesting,
             @Nullable IntFunction<InputMethodBindingController> bindingControllerForTesting) {
         synchronized (ImfLock.class) {
+            mExperimentalConcurrentMultiUserModeEnabled =
+                    experimentalConcurrentMultiUserModeEnabled;
             mContext = context;
             mRes = context.getResources();
             SecureSettingsWrapper.onStart(mContext);
@@ -1379,6 +1237,17 @@
                                     true /* allowIo */);
             thread.start();
             mHandler = Handler.createAsync(thread.getLooper(), this);
+            {
+                final ServiceThread packageMonitorThread =
+                        packageMonitorThreadForTesting != null
+                                ? packageMonitorThreadForTesting
+                                : new ServiceThread(
+                                        PACKAGE_MONITOR_THREAD_NAME,
+                                        Process.THREAD_PRIORITY_FOREGROUND,
+                                        true /* allowIo */);
+                packageMonitorThread.start();
+                mPackageMonitorHandler = Handler.createAsync(packageMonitorThread.getLooper());
+            }
             SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mHandler);
             mImeTrackerService = new ImeTrackerService(serviceThreadForTesting != null
                     ? serviceThreadForTesting.getLooper() : Looper.getMainLooper());
@@ -1403,7 +1272,7 @@
             mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
             @SuppressWarnings("GuardedBy") final IntFunction<InputMethodBindingController>
                     bindingControllerFactory = userId -> new InputMethodBindingController(userId,
-                            InputMethodManagerService.this);
+                    InputMethodManagerService.this);
             mUserDataRepository = new UserDataRepository(mHandler, mUserManagerInternal,
                     bindingControllerForTesting != null ? bindingControllerForTesting
                             : bindingControllerFactory);
@@ -1554,7 +1423,8 @@
         // Note that in b/197848765 we want to see if we can keep the binding alive for better
         // profile switching.
         final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        userData.mBindingController.unbindCurrentMethod();
+        final var bindingController = userData.mBindingController;
+        bindingController.unbindCurrentMethod();
 
         unbindCurrentClientLocked(UnbindReason.SWITCH_USER);
 
@@ -1652,7 +1522,7 @@
                     }
                 }, "Lazily initialize IMMS#mImeDrawsImeNavBarRes");
 
-                mMyPackageMonitor.register(mContext, UserHandle.ALL, mHandler);
+                mMyPackageMonitor.register(mContext, UserHandle.ALL, mPackageMonitorHandler);
                 mSettingsObserver.registerContentObserverLocked(currentUserId);
 
                 final IntentFilter broadcastFilterForAllUsers = new IntentFilter();
@@ -1680,8 +1550,9 @@
 
     /**
      * Returns true iff the caller is identified to be the current input method with the token.
-     * @param token The window token given to the input method when it was started.
-     * @return true if and only if non-null valid token is specified.
+     *
+     * @param token the window token given to the input method when it was started
+     * @return true if and only if non-null valid token is specified
      */
     @GuardedBy("ImfLock.class")
     private boolean calledWithValidTokenLocked(@NonNull IBinder token) {
@@ -1773,9 +1644,10 @@
             // Check if selected IME of current user supports handwriting.
             if (userId == mCurrentUserId) {
                 final var userData = mUserDataRepository.getOrCreate(userId);
-                return userData.mBindingController.supportsStylusHandwriting()
+                final var bindingController = userData.mBindingController;
+                return bindingController.supportsStylusHandwriting()
                         && (!connectionless
-                        || userData.mBindingController.supportsConnectionlessStylusHandwriting());
+                        || bindingController.supportsConnectionlessStylusHandwriting());
             }
             final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
             final InputMethodInfo imi = settings.getMethodMap().get(
@@ -1834,27 +1706,13 @@
     }
 
     /**
-     * Sets current host input token.
-     *
-     * @param callerImeToken the token has been made for the current active input method
-     * @param hostInputToken the host input token of the current active input method
-     */
-    void setCurHostInputToken(@NonNull IBinder callerImeToken, @Nullable IBinder hostInputToken) {
-        synchronized (ImfLock.class) {
-            if (!calledWithValidTokenLocked(callerImeToken)) {
-                return;
-            }
-            mCurHostInputToken = hostInputToken;
-        }
-    }
-
-    /**
      * Gets enabled subtypes of the specified {@link InputMethodInfo}.
      *
-     * @param imiId if null, returns enabled subtypes for the current {@link InputMethodInfo}.
+     * @param imiId                           if null, returns enabled subtypes for the current
+     *                                        {@link InputMethodInfo}
      * @param allowsImplicitlyEnabledSubtypes {@code true} to return the implicitly enabled
-     *                                         subtypes.
-     * @param userId the user ID to be queried about.
+     *                                        subtypes
+     * @param userId                          the user ID to be queried about
      */
     @Override
     public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
@@ -1898,10 +1756,11 @@
      * <p>As a general principle, IPCs from the application process that take
      * {@link IInputMethodClient} will be rejected without this step.</p>
      *
-     * @param client {@link android.os.Binder} proxy that is associated with the singleton instance
-     *               of {@link android.view.inputmethod.InputMethodManager} that runs on the client
-     *               process
-     * @param inputConnection communication channel for the fallback {@link InputConnection}
+     * @param client                {@link android.os.Binder} proxy that is associated with the
+     *                              singleton instance of
+     *                              {@link android.view.inputmethod.InputMethodManager} that runs
+     *                              on the client process
+     * @param inputConnection       communication channel for the fallback {@link InputConnection}
      * @param selfReportedDisplayId self-reported display ID to which the client is associated.
      *                              Whether the client is still allowed to access to this display
      *                              or not needs to be evaluated every time the client interacts
@@ -1926,10 +1785,10 @@
         }
     }
 
-    // TODO(b/325515685): Move this method to InputMethodBindingController
     /**
      * Hide the IME if the removed user is the current user.
      */
+    // TODO(b/325515685): Move this method to InputMethodBindingController
     @GuardedBy("ImfLock.class")
     private void onClientRemoved(ClientState client) {
         clearClientSessionLocked(client);
@@ -1962,7 +1821,6 @@
         return mClientController.getClient(client.asBinder());
     }
 
-    // TODO(b/314150112): Move this to ClientController.
     @GuardedBy("ImfLock.class")
     void unbindCurrentClientLocked(@UnbindReason int unbindClientReason) {
         if (mCurClient != null) {
@@ -1982,7 +1840,14 @@
             // all accessibility too. That means, when input method get disconnected (including
             // switching ime), we also unbind accessibility
             mCurClient.mClient.setActive(false /* active */, false /* fullscreen */);
-            mCurClient.mClient.onUnbindMethod(getSequenceNumberLocked(), unbindClientReason);
+
+            // TODO(b/325515685): make binding controller user independent. Before this change, the
+            //  following dependencies also need to be user independent: mCurClient, mBoundToMethod,
+            //  getCurMethodLocked(), and mMenuController.
+            final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+            final var bindingController = userData.mBindingController;
+            mCurClient.mClient.onUnbindMethod(bindingController.getSequenceNumber(),
+                    unbindClientReason);
             mCurClient.mSessionRequested = false;
             mCurClient.mSessionRequestedForAccessibility = false;
             mCurClient = null;
@@ -2060,12 +1925,15 @@
 
         final boolean restarting = !initial;
         final Binder startInputToken = new Binder();
+        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+        final var bindingController = userData.mBindingController;
         final StartInputInfo info = new StartInputInfo(mCurrentUserId,
                 getCurTokenLocked(),
-                mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting,
-                UserHandle.getUserId(mCurClient.mUid),
+                getCurTokenDisplayIdLocked(), bindingController.getCurId(), startInputReason,
+                restarting, UserHandle.getUserId(mCurClient.mUid),
                 mCurClient.mSelfReportedDisplayId, mImeBindingState.mFocusedWindow, mCurEditorInfo,
-                mImeBindingState.mFocusedWindowSoftInputMode, getSequenceNumberLocked());
+                mImeBindingState.mFocusedWindowSoftInputMode,
+                bindingController.getSequenceNumber());
         mImeTargetWindowMap.put(startInputToken, mImeBindingState.mFocusedWindow);
         mStartInputHistory.addEntry(info);
 
@@ -2076,8 +1944,8 @@
         // INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case.
         if (mCurrentUserId == UserHandle.getUserId(
                 mCurClient.mUid)) {
-            mPackageManagerInternal.grantImplicitAccess(mCurrentUserId,
-                    null /* intent */, UserHandle.getAppId(getCurMethodUidLocked()),
+            mPackageManagerInternal.grantImplicitAccess(mCurrentUserId, null /* intent */,
+                    UserHandle.getAppId(bindingController.getCurMethodUid()),
                     mCurClient.mUid, true /* direct */);
         }
 
@@ -2098,21 +1966,20 @@
                     null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT);
         }
 
-        String curId = getCurIdLocked();
+        final var curId = bindingController.getCurId();
         final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(mCurrentUserId)
                 .getMethodMap().get(curId);
         final boolean suppressesSpellChecker =
                 curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
         final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
                 createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions);
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        if (userData.mBindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) {
+        if (bindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) {
             mHwController.setInkWindowInitializer(new InkWindowInitializer());
         }
         return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
                 session.mSession, accessibilityInputMethodSessions,
                 (session.mChannel != null ? session.mChannel.dup() : null),
-                curId, getSequenceNumberLocked(), suppressesSpellChecker);
+                curId, bindingController.getSequenceNumber(), suppressesSpellChecker);
     }
 
     @GuardedBy("ImfLock.class")
@@ -2164,7 +2031,8 @@
             @NonNull EditorInfo editorInfo, @StartInputFlags int startInputFlags,
             @StartInputReason int startInputReason,
             int unverifiedTargetSdkVersion,
-            @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+            @NonNull ImeOnBackInvokedDispatcher imeDispatcher,
+            @NonNull UserDataRepository.UserData userData) {
 
         // Compute the final shown display ID with validated cs.selfReportedDisplayId for this
         // session & other conditions.
@@ -2205,7 +2073,9 @@
         final boolean connectionWasActive = mCurInputConnection != null;
 
         // Bump up the sequence for this client and attach it.
-        advanceSequenceNumberLocked();
+        final var bindingController = userData.mBindingController;
+        bindingController.advanceSequenceNumber();
+
         mCurClient = cs;
         mCurInputConnection = inputConnection;
         mCurRemoteAccessibilityInputConnection = remoteAccessibilityInputConnection;
@@ -2227,8 +2097,6 @@
         if (connectionIsActive != connectionWasActive) {
             mInputManagerInternal.notifyInputMethodConnectionActive(connectionIsActive);
         }
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-
 
         // If configured, we want to avoid starting up the IME if it is not supposed to be showing
         if (shouldPreventImeStartupLocked(selectedMethodId, startInputFlags,
@@ -2237,14 +2105,16 @@
                 Slog.d(TAG, "Avoiding IME startup and unbinding current input method.");
             }
             invalidateAutofillSessionLocked();
-            userData.mBindingController.unbindCurrentMethod();
+            bindingController.unbindCurrentMethod();
             return InputBindResult.NO_EDITOR;
         }
 
         // Check if the input method is changing.
         // We expect the caller has already verified that the client is allowed to access this
         // display ID.
-        if (isSelectedMethodBoundLocked()) {
+        final String curId = bindingController.getCurId();
+        if (curId != null && curId.equals(bindingController.getSelectedMethodId())
+                && mDisplayIdToShowIme == getCurTokenDisplayIdLocked()) {
             if (cs.mCurSession != null) {
                 // Fast case: if we are already connected to the input method,
                 // then just return it.
@@ -2263,23 +2133,25 @@
                         (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0);
             }
 
-            InputBindResult bindResult = tryReuseConnectionLocked(cs);
+            InputBindResult bindResult = tryReuseConnectionLocked(userData, cs);
             if (bindResult != null) {
                 return bindResult;
             }
         }
 
-        userData.mBindingController.unbindCurrentMethod();
-        return userData.mBindingController.bindCurrentMethod();
+        bindingController.unbindCurrentMethod();
+        return bindingController.bindCurrentMethod();
     }
 
     /**
      * Update the current deviceId and return the relevant imeId for this device.
-     *   1. If the device changes to virtual and its custom IME is not available, then disable IME.
-     *   2. If the device changes to virtual with valid custom IME, then return the custom IME. If
-     *      the old device was default, then store the current imeId so it can be restored.
-     *   3. If the device changes to default, restore the default device IME.
-     *   4. Otherwise keep the current imeId.
+     *
+     * <p>1. If the device changes to virtual and its custom IME is not available, then disable
+     * IME.</p>
+     * <p>2. If the device changes to virtual with valid custom IME, then return the custom IME. If
+     * the old device was default, then store the current imeId so it can be restored.</p>
+     * <p>3. If the device changes to default, restore the default device IME.</p>
+     * <p>4. Otherwise keep the current imeId.</p>
      */
     @GuardedBy("ImfLock.class")
     private String computeCurrentDeviceMethodIdLocked(String currentMethodId) {
@@ -2363,13 +2235,6 @@
     }
 
     @GuardedBy("ImfLock.class")
-    private boolean isSelectedMethodBoundLocked() {
-        String curId = getCurIdLocked();
-        return curId != null && curId.equals(getSelectedMethodIdLocked())
-                && mDisplayIdToShowIme == mCurTokenDisplayId;
-    }
-
-    @GuardedBy("ImfLock.class")
     private void prepareClientSwitchLocked(ClientState cs) {
         // If the client is changing, we need to switch over to the new
         // one.
@@ -2382,8 +2247,10 @@
 
     @GuardedBy("ImfLock.class")
     @Nullable
-    private InputBindResult tryReuseConnectionLocked(@NonNull ClientState cs) {
-        if (hasConnectionLocked()) {
+    private InputBindResult tryReuseConnectionLocked(@NonNull UserDataRepository.UserData userData,
+            @NonNull ClientState cs) {
+        final var bindingController = userData.mBindingController;
+        if (bindingController.hasMainConnection()) {
             if (getCurMethodLocked() != null) {
                 // Return to client, and we will get back with it when
                 // we have had a session made for it.
@@ -2391,9 +2258,12 @@
                 requestClientSessionForAccessibilityLocked(cs);
                 return new InputBindResult(
                         InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
-                        null, null, null, getCurIdLocked(), getSequenceNumberLocked(), false);
+                        null, null, null,
+                        bindingController.getCurId(),
+                        bindingController.getSequenceNumber(), false);
             } else {
-                long bindingDuration = SystemClock.uptimeMillis() - getLastBindTimeLocked();
+                final long lastBindTime = bindingController.getLastBindTime();
+                long bindingDuration = SystemClock.uptimeMillis() - lastBindTime;
                 if (bindingDuration < TIME_TO_RECONNECT) {
                     // In this case we have connected to the service, but
                     // don't yet have its interface.  If it hasn't been too
@@ -2404,7 +2274,9 @@
                     // to see if we can get back in touch with the service.
                     return new InputBindResult(
                             InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
-                            null, null, null, getCurIdLocked(), getSequenceNumberLocked(), false);
+                            null, null, null,
+                            bindingController.getCurId(),
+                            bindingController.getSequenceNumber(), false);
                 } else {
                     EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
                             getSelectedMethodIdLocked(), bindingDuration, 0);
@@ -2416,18 +2288,19 @@
 
     @FunctionalInterface
     interface ImeDisplayValidator {
-        @DisplayImePolicy int getDisplayImePolicy(int displayId);
+        @DisplayImePolicy
+        int getDisplayImePolicy(int displayId);
     }
 
     /**
      * Find the display where the IME should be shown.
      *
-     * @param displayId the ID of the display where the IME client target is.
-     * @param checker instance of {@link ImeDisplayValidator} which is used for
-     *                checking display config to adjust the final target display.
-     * @return The ID of the display where the IME should be shown or
-     *         {@link android.view.Display#INVALID_DISPLAY} if the display has an ImePolicy of
-     *         {@link WindowManager#DISPLAY_IME_POLICY_HIDE}.
+     * @param displayId the ID of the display where the IME client target is
+     * @param checker   instance of {@link ImeDisplayValidator} which is used for
+     *                  checking display config to adjust the final target display
+     * @return the ID of the display where the IME should be shown or
+     * {@link android.view.Display#INVALID_DISPLAY} if the display has an ImePolicy of
+     * {@link WindowManager#DISPLAY_IME_POLICY_HIDE}
      */
     static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) {
         if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) {
@@ -2450,7 +2323,7 @@
     void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token) {
         if (DEBUG) {
             Slog.v(TAG, "Sending attach of token: " + token + " for display: "
-                    + mCurTokenDisplayId);
+                    + getCurTokenDisplayIdLocked());
         }
         inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token),
                 getInputMethodNavButtonFlagsLocked());
@@ -2524,19 +2397,19 @@
         mImeWindowVis = 0;
         mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
         updateSystemUiLocked(mImeWindowVis, mBackDisposition);
-        mCurTokenDisplayId = INVALID_DISPLAY;
-        mCurHostInputToken = null;
+        mAutofillController.onResetSystemUi();
     }
 
     @GuardedBy("ImfLock.class")
     void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) {
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        userData.mBindingController.setSelectedMethodId(null);
+        final var bindingController =
+                mUserDataRepository.getOrCreate(mCurrentUserId).mBindingController;
+        bindingController.setSelectedMethodId(null);
 
         // Callback before clean-up binding states.
         // TODO(b/338461930): Check if this is still necessary or not.
         onUnbindCurrentMethodByReset();
-        userData.mBindingController.unbindCurrentMethod();
+        bindingController.unbindCurrentMethod();
         unbindCurrentClientLocked(unbindClientReason);
     }
 
@@ -2711,7 +2584,7 @@
                             : null;
                     if (mStatusBarManagerInternal != null) {
                         mStatusBarManagerInternal.setIcon(mSlotIme, packageName, iconId, 0,
-                                contentDescription  != null
+                                contentDescription != null
                                         ? contentDescription.toString() : null);
                         mStatusBarManagerInternal.setIconVisibility(mSlotIme, true);
                     }
@@ -2739,9 +2612,10 @@
         }
         // Whether the current display has a navigation bar. When this is false (e.g. emulator),
         // the IME should not draw the IME navigation bar.
+        final int tokenDisplayId = getCurTokenDisplayIdLocked();
         final boolean hasNavigationBar = mWindowManagerInternal
-                .hasNavigationBar(mCurTokenDisplayId != INVALID_DISPLAY
-                        ? mCurTokenDisplayId : DEFAULT_DISPLAY);
+                .hasNavigationBar(tokenDisplayId != INVALID_DISPLAY
+                        ? tokenDisplayId : DEFAULT_DISPLAY);
         final boolean canImeDrawsImeNavBar =
                 mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get() && hasNavigationBar;
         final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked(
@@ -2757,7 +2631,8 @@
         // When the IME switcher dialog is shown, the IME switcher button should be hidden.
         if (mMenuController.getSwitchingDialogLocked() != null) return false;
         // When we are switching IMEs, the IME switcher button should be hidden.
-        if (!Objects.equals(getCurIdLocked(), getSelectedMethodIdLocked())) {
+        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+        if (!Objects.equals(userData.mBindingController.getCurId(), getSelectedMethodIdLocked())) {
             return false;
         }
         if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
@@ -2836,8 +2711,8 @@
             // Note that we still need to update IME status when focusing external display
             // that does not support system decoration and fallback to show IME on default
             // display since it is intentional behavior.
-            if (mCurTokenDisplayId != topFocusedDisplayId
-                    && mCurTokenDisplayId != FALLBACK_DISPLAY_ID) {
+            final int tokenDisplayId = getCurTokenDisplayIdLocked();
+            if (tokenDisplayId != topFocusedDisplayId && tokenDisplayId != FALLBACK_DISPLAY_ID) {
                 return;
             }
             mImeWindowVis = vis;
@@ -2900,7 +2775,7 @@
             Slog.d(TAG, "IME window vis: " + vis
                     + " active: " + (vis & InputMethodService.IME_ACTIVE)
                     + " inv: " + (vis & InputMethodService.IME_INVISIBLE)
-                    + " displayId: " + mCurTokenDisplayId);
+                    + " displayId: " + getCurTokenDisplayIdLocked());
         }
         final IBinder focusedWindowToken = mImeBindingState != null
                 ? mImeBindingState.mFocusedWindow : null;
@@ -2919,15 +2794,17 @@
             } else {
                 vis &= ~InputMethodService.IME_VISIBLE_IMPERCEPTIBLE;
             }
+            final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+            final var curId = userData.mBindingController.getCurId();
             if (mMenuController.getSwitchingDialogLocked() != null
-                    || !Objects.equals(getCurIdLocked(), getSelectedMethodIdLocked())) {
+                    || !Objects.equals(curId, getSelectedMethodIdLocked())) {
                 // When the IME switcher dialog is shown, or we are switching IMEs,
                 // the back button should be in the default state (as if the IME is not shown).
                 backDisposition = InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING;
             }
             final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis);
             if (mStatusBarManagerInternal != null) {
-                mStatusBarManagerInternal.setImeWindowStatus(mCurTokenDisplayId,
+                mStatusBarManagerInternal.setImeWindowStatus(getCurTokenDisplayIdLocked(),
                         getCurTokenLocked(), vis, backDisposition, needsToShowImeSwitcher);
             }
         } finally {
@@ -2941,6 +2818,49 @@
         mMenuController.updateKeyboardFromSettingsLocked();
     }
 
+    /**
+     * This is an experimental implementation used when and only when
+     * {@link #mExperimentalConcurrentMultiUserModeEnabled}.
+     *
+     * <p>Never assume what this method is doing is officially supported. For the canonical and
+     * desired behaviors always refer to single-user code paths such as
+     * {@link #updateInputMethodsFromSettingsLocked(boolean)}.</p>
+     *
+     * <p>Here are examples of missing features.</p>
+     * <ul>
+     *     <li>Subtypes are not supported at all!</li>
+     *     <li>Profiles are not supported.</li>
+     *     <li>
+     *         {@link PackageManager#COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED} is not updated.
+     *     </li>
+     *     <li>{@link #mDeviceIdToShowIme} is ignored.</li>
+     *     <li>{@link #mSwitchingController} is ignored.</li>
+     *     <li>{@link #mHardwareKeyboardShortcutController} is ignored.</li>
+     *     <li>{@link #mPreventImeStartupUnlessTextEditor} is ignored.</li>
+     *     <li>and so on.</li>
+     * </ul>
+     */
+    @GuardedBy("ImfLock.class")
+    void experimentalInitializeVisibleBackgroundUserLocked(@UserIdInt int userId) {
+        if (!mUserManagerInternal.isUserVisible(userId)) {
+            return;
+        }
+        final var settings = InputMethodSettingsRepository.get(userId);
+        String id = settings.getSelectedInputMethod();
+        if (TextUtils.isEmpty(id)) {
+            final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME(
+                    settings.getEnabledInputMethodList());
+            if (imi == null) {
+                return;
+            }
+            id = imi.getId();
+            settings.putSelectedInputMethod(id);
+        }
+        final var userData = mUserDataRepository.getOrCreate(userId);
+        final var bindingController = userData.mBindingController;
+        bindingController.setSelectedMethodId(id);
+    }
+
     @GuardedBy("ImfLock.class")
     void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
         final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
@@ -3615,12 +3535,15 @@
                     "InputMethodManagerService#startInputOrWindowGainedFocus", mDumper);
             final InputBindResult result;
             synchronized (ImfLock.class) {
+                final var userData = mUserDataRepository.getOrCreate(userId);
+                final var bindingController = userData.mBindingController;
                 // If the system is not yet ready, we shouldn't be running third party code.
                 if (!mSystemReady) {
                     return new InputBindResult(
                             InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
                             null /* method */, null /* accessibilitySessions */, null /* channel */,
-                            getSelectedMethodIdLocked(), getSequenceNumberLocked(),
+                            getSelectedMethodIdLocked(),
+                            bindingController.getSequenceNumber(),
                             false /* isInputMethodSuppressingSpellChecker */);
                 }
                 final ClientState cs = mClientController.getClient(client.asBinder());
@@ -3712,7 +3635,7 @@
                     result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
                             client, windowToken, startInputFlags, softInputMode, windowFlags,
                             editorInfo, inputConnection, remoteAccessibilityInputConnection,
-                            unverifiedTargetSdkVersion, userId, imeDispatcher, cs);
+                            unverifiedTargetSdkVersion, userData, imeDispatcher, cs);
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
@@ -3740,7 +3663,7 @@
             @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo editorInfo,
             IRemoteInputConnection inputContext,
             @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
-            int unverifiedTargetSdkVersion, @UserIdInt int userId,
+            int unverifiedTargetSdkVersion, @NonNull UserDataRepository.UserData userData,
             @NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull ClientState cs) {
         if (DEBUG) {
             Slog.v(TAG, "startInputOrWindowGainedFocusInternalLocked: reason="
@@ -3753,7 +3676,7 @@
                     + " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode)
                     + " windowFlags=#" + Integer.toHexString(windowFlags)
                     + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion
-                    + " userId=" + userId
+                    + " userData=" + userData
                     + " imeDispatcher=" + imeDispatcher
                     + " cs=" + cs);
         }
@@ -3782,14 +3705,15 @@
             if (editorInfo != null) {
                 return startInputUncheckedLocked(cs, inputContext,
                         remoteAccessibilityInputConnection, editorInfo, startInputFlags,
-                        startInputReason, unverifiedTargetSdkVersion, imeDispatcher);
+                        startInputReason, unverifiedTargetSdkVersion, imeDispatcher, userData);
             }
             return new InputBindResult(
                     InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
                     null, null, null, null, -1, false);
         }
 
-        mImeBindingState = new ImeBindingState(windowToken, softInputMode, cs, editorInfo);
+        mImeBindingState = new ImeBindingState(userData.mUserId, windowToken, softInputMode, cs,
+                editorInfo);
         mFocusedWindowPerceptible.put(windowToken, true);
 
         // We want to start input before showing the IME, but after closing
@@ -3814,7 +3738,7 @@
                         res = startInputUncheckedLocked(cs, inputContext,
                                 remoteAccessibilityInputConnection, editorInfo, startInputFlags,
                                 startInputReason, unverifiedTargetSdkVersion,
-                                imeDispatcher);
+                                imeDispatcher, userData);
                         didStart = true;
                     }
                     break;
@@ -3828,8 +3752,7 @@
                 // window token removed.
                 // Note that we can trust client's display ID as long as it matches
                 // to the display ID obtained from the window.
-                if (cs.mSelfReportedDisplayId != mCurTokenDisplayId) {
-                    final var userData = mUserDataRepository.getOrCreate(userId);
+                if (cs.mSelfReportedDisplayId != getCurTokenDisplayIdLocked()) {
                     userData.mBindingController.unbindCurrentMethod();
                 }
             }
@@ -3839,7 +3762,7 @@
                 res = startInputUncheckedLocked(cs, inputContext,
                         remoteAccessibilityInputConnection, editorInfo, startInputFlags,
                         startInputReason, unverifiedTargetSdkVersion,
-                        imeDispatcher);
+                        imeDispatcher, userData);
             } else {
                 res = InputBindResult.NULL_EDITOR_INFO;
             }
@@ -3880,10 +3803,10 @@
         if (mCurrentUserId != UserHandle.getUserId(uid)) {
             return false;
         }
-        if (getCurIntentLocked() != null && InputMethodUtils.checkIfPackageBelongsToUid(
-                mPackageManagerInternal,
-                uid,
-                getCurIntentLocked().getComponent().getPackageName())) {
+        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+        final var curIntent = userData.mBindingController.getCurIntent();
+        if (curIntent != null && InputMethodUtils.checkIfPackageBelongsToUid(
+                mPackageManagerInternal, uid, curIntent.getComponent().getPackageName())) {
             return true;
         }
         return false;
@@ -4193,7 +4116,6 @@
      * This is kept due to {@code @UnsupportedAppUsage} in
      * {@link InputMethodManager#getInputMethodWindowVisibleHeight()} and a dependency in
      * {@link InputMethodService#onCreate()}.
-     *
      * @return {@link WindowManagerInternal#getInputMethodWindowVisibleHeight(int)}
      *
      * @deprecated TODO(b/113914148): Check if we can remove this
@@ -4211,7 +4133,7 @@
                 }
                 // This should probably use the caller's display id, but because this is unsupported
                 // and maintained only for compatibility, there's no point in fixing it.
-                curTokenDisplayId = mCurTokenDisplayId;
+                curTokenDisplayId = getCurTokenDisplayIdLocked();
             }
             return mWindowManagerInternal.getInputMethodWindowVisibleHeight(curTokenDisplayId);
         });
@@ -4292,8 +4214,9 @@
         // a new Stylus is detected. If IME supports handwriting, and we don't have
         // handwriting initialized, lets do it now.
         final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+        final var bindingController = userData.mBindingController;
         if (!mHwController.getCurrentRequestId().isPresent()
-                && userData.mBindingController.supportsStylusHandwriting()) {
+                && bindingController.supportsStylusHandwriting()) {
             scheduleResetStylusHandwriting();
         }
     }
@@ -4351,7 +4274,8 @@
     /**
      * Helper method to set a stylus idle-timeout after which handwriting {@code InkWindow}
      * will be removed.
-     * @param timeout to set in milliseconds. To reset to default, use a value <= zero.
+     *
+     * @param timeout to set in milliseconds. To reset to default, use a value <= zero
      */
     @BinderThread
     @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
@@ -4474,9 +4398,11 @@
 
     private void dumpDebug(ProtoOutputStream proto, long fieldId) {
         synchronized (ImfLock.class) {
+            final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+            final var bindingController = userData.mBindingController;
             final long token = proto.start(fieldId);
             proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked());
-            proto.write(CUR_SEQ, getSequenceNumberLocked());
+            proto.write(CUR_SEQ, bindingController.getSequenceNumber());
             proto.write(CUR_CLIENT, Objects.toString(mCurClient));
             mImeBindingState.dumpDebug(proto, mWindowManagerInternal);
             proto.write(LAST_IME_TARGET_WINDOW_NAME,
@@ -4486,13 +4412,13 @@
             if (mCurEditorInfo != null) {
                 mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE);
             }
-            proto.write(CUR_ID, getCurIdLocked());
+            proto.write(CUR_ID, bindingController.getCurId());
             mVisibilityStateComputer.dumpDebug(proto, fieldId);
             proto.write(IN_FULLSCREEN_MODE, mInFullscreenMode);
             proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked()));
-            proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId);
+            proto.write(CUR_TOKEN_DISPLAY_ID, getCurTokenDisplayIdLocked());
             proto.write(SYSTEM_READY, mSystemReady);
-            proto.write(HAVE_CONNECTION, hasConnectionLocked());
+            proto.write(HAVE_CONNECTION, bindingController.hasMainConnection());
             proto.write(BOUND_TO_METHOD, mBoundToMethod);
             proto.write(IS_INTERACTIVE, mIsInteractive);
             proto.write(BACK_DISPOSITION, mBackDisposition);
@@ -4604,7 +4530,8 @@
         final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken);
         final WindowManagerInternal.ImeTargetInfo info =
                 mWindowManagerInternal.onToggleImeRequested(
-                        show, mImeBindingState.mFocusedWindow, requestToken, mCurTokenDisplayId);
+                        show, mImeBindingState.mFocusedWindow, requestToken,
+                        getCurTokenDisplayIdLocked());
         mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
                 mImeBindingState.mFocusedWindowClient, mImeBindingState.mFocusedWindowEditorInfo,
                 info.focusedWindowName, mImeBindingState.mFocusedWindowSoftInputMode, reason,
@@ -4705,7 +4632,7 @@
         SparseArray<IAccessibilityInputMethodSession> disabledSessions = new SparseArray<>();
         for (int i = 0; i < mEnabledAccessibilitySessions.size(); i++) {
             if (!accessibilitySessions.contains(mEnabledAccessibilitySessions.keyAt(i))) {
-                AccessibilitySessionState sessionState  = mEnabledAccessibilitySessions.valueAt(i);
+                AccessibilitySessionState sessionState = mEnabledAccessibilitySessions.valueAt(i);
                 if (sessionState != null) {
                     disabledSessions.append(mEnabledAccessibilitySessions.keyAt(i),
                             sessionState.mSession);
@@ -4863,10 +4790,11 @@
             case MSG_RESET_HANDWRITING: {
                 synchronized (ImfLock.class) {
                     final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-                    if (userData.mBindingController.supportsStylusHandwriting()
+                    final var bindingController = userData.mBindingController;
+                    if (bindingController.supportsStylusHandwriting()
                             && getCurMethodLocked() != null && hasSupportedStylusLocked()) {
                         Slog.d(TAG, "Initializing Handwriting Spy");
-                        mHwController.initializeHandwritingSpy(mCurTokenDisplayId);
+                        mHwController.initializeHandwritingSpy(getCurTokenDisplayIdLocked());
                     } else {
                         mHwController.reset();
                     }
@@ -4889,11 +4817,12 @@
                         return true;
                     }
                     final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+                    final var bindingController = userData.mBindingController;
                     final HandwritingModeController.HandwritingSession session =
                             mHwController.startHandwritingSession(
                                     msg.arg1 /*requestId*/,
                                     msg.arg2 /*pid*/,
-                                    userData.mBindingController.getCurMethodUid(),
+                                    bindingController.getCurMethodUid(),
                                     mImeBindingState.mFocusedWindow);
                     if (session == null) {
                         Slog.e(TAG,
@@ -4943,7 +4872,11 @@
             if (mCurClient == null || mCurClient.mClient == null) {
                 return;
             }
-            if (mImePlatformCompatUtils.shouldUseSetInteractiveProtocol(getCurMethodUidLocked())) {
+            // TODO(b/325515685): user data must be retrieved by a userId parameter
+            final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+            final var bindingController = userData.mBindingController;
+            if (mImePlatformCompatUtils.shouldUseSetInteractiveProtocol(
+                    bindingController.getCurMethodUid())) {
                 // Handle IME visibility when interactive changed before finishing the input to
                 // ensure we preserve the last state as possible.
                 final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.onInteractiveChanged(
@@ -5078,30 +5011,9 @@
             return;
         }
         mMethodMapUpdateCount++;
-        mMyPackageMonitor.clearKnownImePackageNamesLocked();
 
         final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
 
-        // Construct the set of possible IME packages for onPackageChanged() to avoid false
-        // negatives when the package state remains to be the same but only the component state is
-        // changed.
-        {
-            // Here we intentionally use PackageManager.MATCH_DISABLED_COMPONENTS since the purpose
-            // of this query is to avoid false negatives.  PackageManager.MATCH_ALL could be more
-            // conservative, but it seems we cannot use it for now (Issue 35176630).
-            final List<ResolveInfo> allInputMethodServices =
-                    mContext.getPackageManager().queryIntentServicesAsUser(
-                            new Intent(InputMethod.SERVICE_INTERFACE),
-                            PackageManager.MATCH_DISABLED_COMPONENTS, settings.getUserId());
-            final int numImes = allInputMethodServices.size();
-            for (int i = 0; i < numImes; ++i) {
-                final ServiceInfo si = allInputMethodServices.get(i).serviceInfo;
-                if (android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
-                    mMyPackageMonitor.addKnownImePackageNameLocked(si.packageName);
-                }
-            }
-        }
-
         boolean reenableMinimumNonAuxSystemImes = false;
         // TODO: The following code should find better place to live.
         if (!resetDefaultEnabledIme) {
@@ -5188,7 +5100,8 @@
     @GuardedBy("ImfLock.class")
     void sendOnNavButtonFlagsChangedLocked() {
         final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        final IInputMethodInvoker curMethod = userData.mBindingController.getCurMethod();
+        final var bindingController = userData.mBindingController;
+        final IInputMethodInvoker curMethod = bindingController.getCurMethod();
         if (curMethod == null) {
             // No need to send the data if the IME is not yet bound.
             return;
@@ -5233,10 +5146,10 @@
     /**
      * Enable or disable the given IME by updating {@link Settings.Secure#ENABLED_INPUT_METHODS}.
      *
-     * @param id ID of the IME is to be manipulated. It is OK to pass IME ID that is currently not
-     *           recognized by the system.
-     * @param enabled {@code true} if {@code id} needs to be enabled.
-     * @return {@code true} if the IME was previously enabled. {@code false} otherwise.
+     * @param id      ID of the IME is to be manipulated. It is OK to pass IME ID that is currently
+     *                not recognized by the system
+     * @param enabled {@code true} if {@code id} needs to be enabled
+     * @return {@code true} if the IME was previously enabled
      */
     @GuardedBy("ImfLock.class")
     private boolean setInputMethodEnabledLocked(String id, boolean enabled) {
@@ -5343,8 +5256,8 @@
     /**
      * Gets the current subtype of this input method.
      *
-     * @param userId User ID to be queried about.
-     * @return The current {@link InputMethodSubtype} for the specified user.
+     * @param userId User ID to be queried about
+     * @return the current {@link InputMethodSubtype} for the specified user
      */
     @Nullable
     @Override
@@ -5418,7 +5331,8 @@
 
     /**
      * Returns the default {@link InputMethodInfo} for the specific userId.
-     * @param userId user ID to query.
+     *
+     * @param userId user ID to query
      */
     @GuardedBy("ImfLock.class")
     private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) {
@@ -5440,7 +5354,7 @@
         }
         if (!settings.getMethodMap().containsKey(imeId)
                 || !settings.getEnabledInputMethodList().contains(
-                        settings.getMethodMap().get(imeId))) {
+                settings.getMethodMap().get(imeId))) {
             return false; // IME is not found or not enabled.
         }
         settings.putSelectedInputMethod(imeId);
@@ -5452,11 +5366,11 @@
      * Filter the access to the input method by rules of the package visibility. Return {@code true}
      * if the given input method is the currently selected one or visible to the caller.
      *
-     * @param targetPkgName The package name of input method to check.
-     * @param callingUid The caller that is going to access the input method.
-     * @param userId The user ID where the input method resides.
-     * @param settings The input method settings under the given user ID.
-     * @return {@code true} if caller is able to access the input method.
+     * @param targetPkgName the package name of input method to check
+     * @param callingUid    the caller that is going to access the input method
+     * @param userId        the user ID where the input method resides
+     * @param settings      the input method settings under the given user ID
+     * @return {@code true} if caller is able to access the input method
      */
     private boolean canCallerAccessInputMethod(@NonNull String targetPkgName, int callingUid,
             @UserIdInt int userId, @NonNull InputMethodSettings settings) {
@@ -5550,7 +5464,7 @@
 
         @Override
         public void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
-                InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb) {
+                InlineSuggestionsRequestInfo requestInfo, InlineSuggestionsRequestCallback cb) {
             // Get the device global touch exploration state before lock to avoid deadlock.
             final boolean touchExplorationEnabled = AccessibilityManagerInternal.get()
                     .isTouchExplorationEnabled(userId);
@@ -5618,14 +5532,17 @@
 
         @Override
         public boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
-                int displayId) {
+                int displayId, @UserIdInt int userId) {
             //TODO(b/150843766): Check if Input Token is valid.
             final IBinder curHostInputToken;
             synchronized (ImfLock.class) {
-                if (displayId != mCurTokenDisplayId || mCurHostInputToken == null) {
+                if (displayId != getCurTokenDisplayIdLocked()) {
                     return false;
                 }
-                curHostInputToken = mCurHostInputToken;
+                curHostInputToken = mAutofillController.getCurHostInputToken();
+                if (curHostInputToken == null) {
+                    return false;
+                }
             }
             return mInputManagerInternal.transferTouchGesture(sourceInputToken, curHostInputToken);
         }
@@ -5670,6 +5587,8 @@
         public void onSessionForAccessibilityCreated(int accessibilityConnectionId,
                 IAccessibilityInputMethodSession session, @UserIdInt int userId) {
             synchronized (ImfLock.class) {
+                final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+                final var bindingController = userData.mBindingController;
                 // TODO(b/305829876): Implement user ID verification
                 if (mCurClient != null) {
                     clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId);
@@ -5691,8 +5610,10 @@
                                     mCurClient.mAccessibilitySessions);
                     final InputBindResult res = new InputBindResult(
                             InputBindResult.ResultCode.SUCCESS_WITH_ACCESSIBILITY_SESSION,
-                            imeSession, accessibilityInputMethodSessions, null, getCurIdLocked(),
-                            getSequenceNumberLocked(), false);
+                            imeSession, accessibilityInputMethodSessions, /* channel= */ null,
+                            bindingController.getCurId(),
+                            bindingController.getSequenceNumber(),
+                            /* isInputMethodSuppressingSpellChecker= */ false);
                     mCurClient.mClient.onBindAccessibilityService(res, accessibilityConnectionId);
                 }
             }
@@ -5702,6 +5623,8 @@
         public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId,
                 @UserIdInt int userId) {
             synchronized (ImfLock.class) {
+                final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+                final var bindingController = userData.mBindingController;
                 // TODO(b/305829876): Implement user ID verification
                 if (mCurClient != null) {
                     if (DEBUG) {
@@ -5711,7 +5634,7 @@
                     // A11yManagerService unbinds the disabled accessibility service. We don't need
                     // to do it here.
                     mCurClient.mClient.onUnbindAccessibilityService(
-                            getSequenceNumberLocked(),
+                            bindingController.getSequenceNumber(),
                             accessibilityConnectionId);
                 }
                 // We only have sessions when we bound to an input method. Remove this session
@@ -5921,13 +5844,11 @@
             @SuppressWarnings("GuardedBy") Consumer<ClientState> clientControllerDump = c -> {
                 p.println("  " + c + ":");
                 p.println("    client=" + c.mClient);
-
                 p.println("    fallbackInputConnection="
                         + c.mFallbackInputConnection);
                 p.println("    sessionRequested="
                         + c.mSessionRequested);
-                p.println(
-                        "    sessionRequestedForAccessibility="
+                p.println("    sessionRequestedForAccessibility="
                                 + c.mSessionRequestedForAccessibility);
                 p.println("    curSession=" + c.mCurSession);
                 p.println("    selfReportedDisplayId=" + c.mSelfReportedDisplayId);
@@ -5935,26 +5856,44 @@
                 p.println("    pid=" + c.mPid);
             };
             mClientController.forAllClients(clientControllerDump);
+            final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+            final var bindingController = userData.mBindingController;
             p.println("  mCurrentUserId=" + mCurrentUserId);
             p.println("  mCurMethodId=" + getSelectedMethodIdLocked());
             client = mCurClient;
-            p.println("  mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked());
+            p.println("  mCurClient=" + client + " mCurSeq="
+                    + bindingController.getSequenceNumber());
             p.println("  mFocusedWindowPerceptible=" + mFocusedWindowPerceptible);
-            mImeBindingState.dump("  ", p);
-            final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-            p.println("  mCurId=" + getCurIdLocked() + " mHaveConnection=" + hasConnectionLocked()
+            mImeBindingState.dump(/* prefix= */ "  ", p);
+
+            p.println("  mCurId=" + bindingController.getCurId()
+                    + " mHaveConnection=" + bindingController.hasMainConnection()
                     + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound="
-                    + userData.mBindingController.isVisibleBound());
+                    + bindingController.isVisibleBound());
+
+            p.println("  mUserDataRepository=");
+            // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
+            @SuppressWarnings("GuardedBy") Consumer<UserDataRepository.UserData> userDataDump =
+                    u -> {
+                        p.println("    mUserId=" + u.mUserId);
+                        p.println("      hasMainConnection="
+                                + u.mBindingController.hasMainConnection());
+                        p.println("      isVisibleBound=" + u.mBindingController.isVisibleBound());
+                    };
+            mUserDataRepository.forAllUserData(userDataDump);
+
             p.println("  mCurToken=" + getCurTokenLocked());
-            p.println("  mCurTokenDisplayId=" + mCurTokenDisplayId);
-            p.println("  mCurHostInputToken=" + mCurHostInputToken);
-            p.println("  mCurIntent=" + getCurIntentLocked());
+            p.println("  mCurTokenDisplayId=" + getCurTokenDisplayIdLocked());
+            p.println("  mCurHostInputToken=" + mAutofillController.getCurHostInputToken());
+            p.println("  mCurIntent=" + bindingController.getCurIntent());
             method = getCurMethodLocked();
             p.println("  mCurMethod=" + getCurMethodLocked());
             p.println("  mEnabledSession=" + mEnabledSession);
             mVisibilityStateComputer.dump(pw, "  ");
             p.println("  mInFullscreenMode=" + mInFullscreenMode);
             p.println("  mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive);
+            p.println("  mExperimentalConcurrentMultiUserModeEnabled="
+                    + mExperimentalConcurrentMultiUserModeEnabled);
             p.println("  ENABLE_HIDE_IME_CAPTION_BAR="
                     + InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR);
             p.println("  mSettingsObserver=" + mSettingsObserver);
@@ -6187,8 +6126,9 @@
 
     /**
      * Handles {@code adb shell ime list}.
-     * @param shellCommand {@link ShellCommand} object that is handling this command.
-     * @return Exit code of the command.
+     *
+     * @param shellCommand {@link ShellCommand} object that is handling this command
+     * @return exit code of the command
      */
     @BinderThread
     @ShellCommandResult
@@ -6246,9 +6186,9 @@
     /**
      * Handles {@code adb shell ime enable} and {@code adb shell ime disable}.
      *
-     * @param shellCommand {@link ShellCommand} object that is handling this command.
-     * @param enabled      {@code true} if the command was {@code adb shell ime enable}.
-     * @return Exit code of the command.
+     * @param shellCommand {@link ShellCommand} object that is handling this command
+     * @param enabled      {@code true} if the command was {@code adb shell ime enable}
+     * @return exit code of the command
      */
     @BinderThread
     @ShellCommandResult
@@ -6283,8 +6223,8 @@
      * {@link ShellCommand#getNextArg()} and {@link ShellCommand#getNextArgRequired()} for the
      * main arguments.</p>
      *
-     * @param shellCommand {@link ShellCommand} from which options should be obtained.
-     * @return User ID to be resolved. {@link UserHandle#CURRENT} if not specified.
+     * @param shellCommand {@link ShellCommand} from which options should be obtained
+     * @return user ID to be resolved. {@link UserHandle#CURRENT} if not specified
      */
     @BinderThread
     @UserIdInt
@@ -6306,12 +6246,12 @@
     /**
      * Handles core logic of {@code adb shell ime enable} and {@code adb shell ime disable}.
      *
-     * @param userId user ID specified to the command.  Pseudo user IDs are not supported.
-     * @param imeId IME ID specified to the command.
-     * @param enabled {@code true} for {@code adb shell ime enable}. {@code false} otherwise.
-     * @param out {@link PrintWriter} to output standard messages.
-     * @param error {@link PrintWriter} to output error messages.
-     * @return {@code false} if it fails to enable the IME.  {@code false} otherwise.
+     * @param userId  user ID specified to the command (pseudo user IDs are not supported)
+     * @param imeId   IME ID specified to the command
+     * @param enabled {@code true} for {@code adb shell ime enable}
+     * @param out     {@link PrintWriter} to output standard messages
+     * @param error   {@link PrintWriter} to output error messages
+     * @return {@code false} if it fails to enable the IME
      */
     @BinderThread
     @GuardedBy("ImfLock.class")
@@ -6369,7 +6309,7 @@
     /**
      * Handles {@code adb shell ime set}.
      *
-     * @param shellCommand {@link ShellCommand} object that is handling this command.
+     * @param shellCommand {@link ShellCommand} object that is handling this command
      * @return Exit code of the command.
      */
     @BinderThread
@@ -6412,7 +6352,8 @@
 
     /**
      * Handles {@code adb shell ime reset-ime}.
-     * @param shellCommand {@link ShellCommand} object that is handling this command.
+     *
+     * @param shellCommand {@link ShellCommand} object that is handling this command
      * @return Exit code of the command.
      */
     @BinderThread
@@ -6428,8 +6369,8 @@
                         continue;
                     }
                     // Skip on headless user
-                    if (USER_TYPE_SYSTEM_HEADLESS.equals(
-                            mUserManagerInternal.getUserInfo(userId).userType)) {
+                    final var userInfo = mUserManagerInternal.getUserInfo(userId);
+                    if (userInfo != null && USER_TYPE_SYSTEM_HEADLESS.equals(userInfo.userType)) {
                         continue;
                     }
                     final String nextIme;
@@ -6439,7 +6380,8 @@
                         hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
                                 SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
                         final var userData = mUserDataRepository.getOrCreate(userId);
-                        userData.mBindingController.unbindCurrentMethod();
+                        final var bindingController = userData.mBindingController;
+                        bindingController.unbindCurrentMethod();
 
                         // Enable default IMEs, disable others
                         var toDisable = settings.getEnabledInputMethodList();
@@ -6492,7 +6434,8 @@
 
     /**
      * Handles {@code adb shell cmd input_method tracing start/stop/save-for-bugreport}.
-     * @param shellCommand {@link ShellCommand} object that is handling this command.
+     *
+     * @param shellCommand {@link ShellCommand} object that is handling this command
      * @return Exit code of the command.
      */
     @BinderThread
@@ -6537,9 +6480,9 @@
 
     /**
      * @param userId the actual user handle obtained by {@link UserHandle#getIdentifier()}
-     * and *not* pseudo ids like {@link UserHandle#USER_ALL etc}.
-     * @return {@code true} if userId has debugging privileges.
-     * i.e. {@link UserManager#DISALLOW_DEBUGGING_FEATURES} is {@code false}.
+     *               and *not* pseudo ids like {@link UserHandle#USER_ALL etc}
+     * @return {@code true} if userId has debugging privileges
+     * i.e. {@link UserManager#DISALLOW_DEBUGGING_FEATURES} is {@code false}
      */
     private boolean userHasDebugPriv(@UserIdInt int userId, ShellCommand shellCommand) {
         if (mUserManagerInternal.hasUserRestriction(
@@ -6560,8 +6503,8 @@
     /**
      * Creates an IME request tracking token for the current focused client.
      *
-     * @param show whether this is a show or a hide request.
-     * @param reason the reason why the IME request was created.
+     * @param show   whether this is a show or a hide request
+     * @param reason the reason why the IME request was created
      */
     @NonNull
     private ImeTracker.Token createStatsTokenForFocusedClient(boolean show,
@@ -6583,6 +6526,7 @@
         private final InputMethodManagerService mImms;
         @NonNull
         private final IBinder mToken;
+
         InputMethodPrivilegedOperationsImpl(InputMethodManagerService imms,
                 @NonNull IBinder token) {
             mImms = imms;
@@ -6611,8 +6555,7 @@
         @Override
         public void createInputContentUriToken(Uri contentUri, String packageName,
                 AndroidFuture future /* T=IBinder */) {
-            @SuppressWarnings("unchecked")
-            final AndroidFuture<IBinder> typedFuture = future;
+            @SuppressWarnings("unchecked") final AndroidFuture<IBinder> typedFuture = future;
             try {
                 typedFuture.complete(mImms.createInputContentUriToken(
                         mToken, contentUri, packageName).asBinder());
@@ -6630,8 +6573,7 @@
         @BinderThread
         @Override
         public void setInputMethod(String id, AndroidFuture future /* T=Void */) {
-            @SuppressWarnings("unchecked")
-            final AndroidFuture<Void> typedFuture = future;
+            @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
             try {
                 mImms.setInputMethod(mToken, id);
                 typedFuture.complete(null);
@@ -6644,8 +6586,7 @@
         @Override
         public void setInputMethodAndSubtype(String id, InputMethodSubtype subtype,
                 AndroidFuture future /* T=Void */) {
-            @SuppressWarnings("unchecked")
-            final AndroidFuture<Void> typedFuture = future;
+            @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
             try {
                 mImms.setInputMethodAndSubtype(mToken, id, subtype);
                 typedFuture.complete(null);
@@ -6659,8 +6600,7 @@
         public void hideMySoftInput(@NonNull ImeTracker.Token statsToken,
                 @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason,
                 AndroidFuture future /* T=Void */) {
-            @SuppressWarnings("unchecked")
-            final AndroidFuture<Void> typedFuture = future;
+            @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
             try {
                 mImms.hideMySoftInput(mToken, statsToken, flags, reason);
                 typedFuture.complete(null);
@@ -6674,8 +6614,7 @@
         public void showMySoftInput(@NonNull ImeTracker.Token statsToken,
                 @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason,
                 AndroidFuture future /* T=Void */) {
-            @SuppressWarnings("unchecked")
-            final AndroidFuture<Void> typedFuture = future;
+            @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
             try {
                 mImms.showMySoftInput(mToken, statsToken, flags, reason);
                 typedFuture.complete(null);
@@ -6693,8 +6632,7 @@
         @BinderThread
         @Override
         public void switchToPreviousInputMethod(AndroidFuture future /* T=Boolean */) {
-            @SuppressWarnings("unchecked")
-            final AndroidFuture<Boolean> typedFuture = future;
+            @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
             try {
                 typedFuture.complete(mImms.switchToPreviousInputMethod(mToken));
             } catch (Throwable e) {
@@ -6706,8 +6644,7 @@
         @Override
         public void switchToNextInputMethod(boolean onlyCurrentIme,
                 AndroidFuture future /* T=Boolean */) {
-            @SuppressWarnings("unchecked")
-            final AndroidFuture<Boolean> typedFuture = future;
+            @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
             try {
                 typedFuture.complete(mImms.switchToNextInputMethod(mToken, onlyCurrentIme));
             } catch (Throwable e) {
@@ -6718,8 +6655,7 @@
         @BinderThread
         @Override
         public void shouldOfferSwitchingToNextInputMethod(AndroidFuture future /* T=Boolean */) {
-            @SuppressWarnings("unchecked")
-            final AndroidFuture<Boolean> typedFuture = future;
+            @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
             try {
                 typedFuture.complete(mImms.shouldOfferSwitchingToNextInputMethod(mToken));
             } catch (Throwable e) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMap.java b/services/core/java/com/android/server/inputmethod/InputMethodMap.java
index a8e5e2e..bab21e8 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMap.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMap.java
@@ -23,6 +23,7 @@
 import android.util.ArrayMap;
 import android.view.inputmethod.InputMethodInfo;
 
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -75,4 +76,61 @@
     int size() {
         return mMap.size();
     }
+
+    @AnyThread
+    @NonNull
+    public InputMethodMap applyAdditionalSubtypes(
+            @NonNull AdditionalSubtypeMap additionalSubtypeMap) {
+        if (additionalSubtypeMap.isEmpty()) {
+            return this;
+        }
+        final int size = size();
+        final ArrayMap<String, InputMethodInfo> newMethodMap = new ArrayMap<>(size);
+        boolean updated = false;
+        for (int i = 0; i < size; ++i) {
+            final var imi = valueAt(i);
+            final var imeId = imi.getId();
+            final var newAdditionalSubtypes = additionalSubtypeMap.get(imeId);
+            if (newAdditionalSubtypes == null || newAdditionalSubtypes.isEmpty()) {
+                newMethodMap.put(imi.getId(), imi);
+            } else {
+                newMethodMap.put(imi.getId(), new InputMethodInfo(imi, newAdditionalSubtypes));
+                updated = true;
+            }
+        }
+        return updated ? InputMethodMap.of(newMethodMap) : this;
+    }
+
+    /**
+     * Compares the given two {@link InputMethodMap} instances to see if they contain the same data
+     * or not.
+     *
+     * @param map1 {@link InputMethodMap} to be compared with
+     * @param map2 {@link InputMethodMap} to be compared with
+     * @return {@code true} if both {@link InputMethodMap} instances contain exactly the same data
+     */
+    @AnyThread
+    static boolean areSame(@NonNull InputMethodMap map1, @NonNull InputMethodMap map2) {
+        if (map1 == map2) {
+            return true;
+        }
+        final int size = map1.size();
+        if (size != map2.size()) {
+            return false;
+        }
+        for (int i = 0; i < size; ++i) {
+            final var imi1 = map1.valueAt(i);
+            final var imeId = imi1.getId();
+            final var imi2 = map2.get(imeId);
+            if (imi2 == null) {
+                return false;
+            }
+            final var marshaled1 = InputMethodInfoUtils.marshal(imi1);
+            final var marshaled2 = InputMethodInfoUtils.marshal(imi2);
+            if (!Arrays.equals(marshaled1, marshaled2)) {
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 825cfcb..2b19d3e 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -96,5 +96,10 @@
             mUserId = userId;
             mBindingController = bindingController;
         }
+
+        @Override
+        public String toString() {
+            return "UserData{" + "mUserId=" + mUserId + '}';
+        }
     }
 }
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
index 0049213..d932bd4 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -32,6 +32,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.HandlerThread;
 import android.os.LocaleList;
@@ -101,6 +102,11 @@
     // the application setting the app-locale itself.
     private final SharedPreferences mDelegateAppLocalePackages;
     private final BroadcastReceiver mUserMonitor;
+    // To determine whether an app is pre-archived, check for Intent.EXTRA_ARCHIVAL upon receiving
+    // the initial PACKAGE_ADDED broadcast. If it is indeed pre-archived, perform the data
+    // restoration during the second PACKAGE_ADDED broadcast, which is sent subsequently when the
+    // app is installed.
+    private final Set<String> mPkgsToRestore;
 
     LocaleManagerBackupHelper(LocaleManagerService localeManagerService,
             PackageManager packageManager, HandlerThread broadcastHandlerThread) {
@@ -119,6 +125,7 @@
         mStagedData = stagedData;
         mDelegateAppLocalePackages = delegateAppLocalePackages != null ? delegateAppLocalePackages
                 : createPersistedInfo();
+        mPkgsToRestore = new ArraySet<>();
 
         mUserMonitor = new UserMonitor();
         IntentFilter filter = new IntentFilter();
@@ -251,6 +258,9 @@
                 LocalesInfo localesInfo = pkgStates.get(pkgName);
                 // Check if the application is already installed for the concerned user.
                 if (isPackageInstalledForUser(pkgName, userId)) {
+                    if (mPkgsToRestore != null) {
+                        mPkgsToRestore.remove(pkgName);
+                    }
                     // Don't apply the restore if the locales have already been set for the app.
                     checkExistingLocalesAndApplyRestore(pkgName, localesInfo, userId);
                 } else {
@@ -279,23 +289,18 @@
 
     /**
      * <p><b>Note:</b> This is invoked by service's common monitor
-     * {@link LocaleManagerServicePackageMonitor#onPackageAdded} when a new package is
+     * {@link LocaleManagerServicePackageMonitor#onPackageAddedWithExtras} when a new package is
      * added on device.
      */
-    void onPackageAdded(String packageName, int uid) {
-        try {
-            synchronized (mStagedDataLock) {
-                cleanStagedDataForOldEntriesLocked();
-
-                int userId = UserHandle.getUserId(uid);
-                if (mStagedData.contains(userId)) {
-                    // Perform lazy restore only if the staged data exists.
-                    doLazyRestoreLocked(packageName, userId);
-                }
+    void onPackageAddedWithExtras(String packageName, int uid, Bundle extras) {
+        boolean archived = false;
+        if (extras != null) {
+            archived = extras.getBoolean(Intent.EXTRA_ARCHIVAL, false);
+            if (archived && mPkgsToRestore != null) {
+                mPkgsToRestore.add(packageName);
             }
-        } catch (Exception e) {
-            Slog.e(TAG, "Exception in onPackageAdded.", e);
         }
+        checkStageDataAndApplyRestore(packageName, uid);
     }
 
     /**
@@ -305,6 +310,10 @@
      */
     void onPackageUpdateFinished(String packageName, int uid) {
         int userId = UserHandle.getUserId(uid);
+        if (mPkgsToRestore != null && mPkgsToRestore.contains(packageName)) {
+            mPkgsToRestore.remove(packageName);
+            checkStageDataAndApplyRestore(packageName, uid);
+        }
         cleanApplicationLocalesIfNeeded(packageName, userId);
     }
 
@@ -338,6 +347,25 @@
         }
     }
 
+    private void checkStageDataAndApplyRestore(String packageName, int uid) {
+        try {
+            synchronized (mStagedDataLock) {
+                cleanStagedDataForOldEntriesLocked();
+
+                int userId = UserHandle.getUserId(uid);
+                if (mStagedData.contains(userId)) {
+                    if (mPkgsToRestore != null) {
+                        mPkgsToRestore.remove(packageName);
+                    }
+                    // Perform lazy restore only if the staged data exists.
+                    doLazyRestoreLocked(packageName, userId);
+                }
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, "Exception in onPackageAdded.", e);
+        }
+    }
+
     private boolean isPackageInstalledForUser(String packageName, int userId) {
         PackageInfo pkgInfo = null;
         try {
diff --git a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
index ecd3614..e0a050f 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
@@ -17,6 +17,7 @@
 package com.android.server.locales;
 
 import android.annotation.NonNull;
+import android.os.Bundle;
 import android.os.UserHandle;
 
 import com.android.internal.content.PackageMonitor;
@@ -48,8 +49,8 @@
     }
 
     @Override
-    public void onPackageAdded(String packageName, int uid) {
-        mBackupHelper.onPackageAdded(packageName, uid);
+    public void onPackageAddedWithExtras(String packageName, int uid, Bundle extras) {
+        mBackupHelper.onPackageAddedWithExtras(packageName, uid, extras);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 5731161..17f8abe 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -78,10 +78,14 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
+import java.util.PriorityQueue;
+import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 
 /**
@@ -152,6 +156,16 @@
     private final ScheduledThreadPoolExecutor mDailyMetricTimer =
             new ScheduledThreadPoolExecutor(1);
 
+    // A queue of reliable message records for duplicate detection
+    private final PriorityQueue<ReliableMessageRecord> mReliableMessageRecordQueue =
+            new PriorityQueue<ReliableMessageRecord>(
+                    (ReliableMessageRecord left, ReliableMessageRecord right) -> {
+                        return Long.compare(left.getTimestamp(), right.getTimestamp());
+                    });
+
+    // The test mode manager that manages behaviors during test mode.
+    private final TestModeManager mTestModeManager = new TestModeManager();
+
     // The period of the recurring time
     private static final int PERIOD_METRIC_QUERY_DAYS = 1;
 
@@ -164,6 +178,9 @@
     private boolean mIsBtScanningEnabled = false;
     private boolean mIsBtMainEnabled = false;
 
+    // True if test mode is enabled for the Context Hub
+    private AtomicBoolean mIsTestModeEnabled = new AtomicBoolean(false);
+
     // A hashmap used to record if a contexthub is waiting for daily query
     private Set<Integer> mMetricQueryPendingContextHubIds =
             Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
@@ -210,8 +227,17 @@
         @Override
         public void handleNanoappMessage(short hostEndpointId, NanoAppMessage message,
                 List<String> nanoappPermissions, List<String> messagePermissions) {
-            handleClientMessageCallback(mContextHubId, hostEndpointId, message, nanoappPermissions,
-                    messagePermissions);
+            if (Flags.reliableMessageImplementation()
+                    && Flags.reliableMessageTestModeBehavior()
+                    && mIsTestModeEnabled.get()
+                    && mTestModeManager.handleNanoappMessage(mContextHubId, hostEndpointId,
+                            message, nanoappPermissions, messagePermissions)) {
+                // The TestModeManager handled the nanoapp message, so return here.
+                return;
+            }
+
+            handleClientMessageCallback(mContextHubId, hostEndpointId, message,
+                    nanoappPermissions, messagePermissions);
         }
 
         @Override
@@ -228,6 +254,111 @@
         }
     }
 
+    /**
+     * Records a reliable message from a nanoapp for duplicate detection.
+     */
+    private static class ReliableMessageRecord {
+        public static final int TIMEOUT_NS = 1000000000;
+
+        public int mContextHubId;
+        public long mTimestamp;
+        public int mMessageSequenceNumber;
+        byte mErrorCode;
+
+        ReliableMessageRecord(int contextHubId, long timestamp,
+                int messageSequenceNumber, byte errorCode) {
+            mContextHubId = contextHubId;
+            mTimestamp = timestamp;
+            mMessageSequenceNumber = messageSequenceNumber;
+            mErrorCode = errorCode;
+        }
+
+        public int getContextHubId() {
+            return mContextHubId;
+        }
+
+        public long getTimestamp() {
+            return mTimestamp;
+        }
+
+        public int getMessageSequenceNumber() {
+            return mMessageSequenceNumber;
+        }
+
+        public byte getErrorCode() {
+            return mErrorCode;
+        }
+
+        public void setErrorCode(byte errorCode) {
+            mErrorCode = errorCode;
+        }
+
+        public boolean isExpired() {
+            return mTimestamp + TIMEOUT_NS < SystemClock.elapsedRealtimeNanos();
+        }
+    }
+
+    /**
+     * A class to manage behaviors during test mode. This is used for testing.
+     */
+    private class TestModeManager {
+        /**
+         * Probability (in percent) of duplicating a message.
+         */
+        private static final int MESSAGE_DUPLICATION_PROBABILITY_PERCENT = 50;
+
+        /**
+         * The number of total messages to send when the duplicate event happens.
+         */
+        private static final int NUM_MESSAGES_TO_DUPLICATE = 3;
+
+        /**
+         * A probability percent for a certain event.
+         */
+        private static final int MAX_PROBABILITY_PERCENT = 100;
+
+        /**
+         * Random number generator.
+         */
+        private Random mRandom = new Random();
+
+        /**
+         * @see ContextHubServiceCallback.handleNanoappMessage
+         * @return whether the message was handled
+         */
+        public boolean handleNanoappMessage(int contextHubId,
+                short hostEndpointId, NanoAppMessage message,
+                List<String> nanoappPermissions, List<String> messagePermissions) {
+            if (!message.isReliable()) {
+                return false;
+            }
+
+            if (Flags.reliableMessageDuplicateDetectionService()
+                && didEventHappen(MESSAGE_DUPLICATION_PROBABILITY_PERCENT)) {
+                Log.i(TAG, "[TEST MODE] Duplicating message ("
+                        + NUM_MESSAGES_TO_DUPLICATE
+                        + " sends) with message sequence number: "
+                        + message.getMessageSequenceNumber());
+                for (int i = 0; i < NUM_MESSAGES_TO_DUPLICATE; ++i) {
+                    handleClientMessageCallback(contextHubId, hostEndpointId,
+                            message, nanoappPermissions, messagePermissions);
+                }
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * Returns true if the event with percentPercent did happen.
+         *
+         * @param probabilityPercent the percent probability of the event.
+         * @return true if the event happened, false otherwise.
+         */
+        private boolean didEventHappen(int probabilityPercent) {
+            return mRandom.nextInt(MAX_PROBABILITY_PERCENT) < probabilityPercent;
+        }
+    }
+
     public ContextHubService(Context context, IContextHubWrapper contextHubWrapper) {
         Log.i(TAG, "Starting Context Hub Service init");
         mContext = context;
@@ -563,6 +694,8 @@
      * Resets the settings. Called when a context hub restarts or the AIDL HAL dies
      */
     private void resetSettings() {
+        mIsTestModeEnabled.set(false);
+
         sendLocationSettingUpdate();
         sendWifiSettingUpdate(/* forceUpdate= */ true);
         sendAirplaneModeSettingUpdate();
@@ -854,14 +987,76 @@
     private void handleClientMessageCallback(int contextHubId, short hostEndpointId,
             NanoAppMessage message, List<String> nanoappPermissions,
             List<String> messagePermissions) {
-        byte errorCode = mClientManager.onMessageFromNanoApp(contextHubId, hostEndpointId, message,
-                nanoappPermissions, messagePermissions);
-        if (message.isReliable() && errorCode != ErrorCode.OK) {
-            sendMessageDeliveryStatusToContextHub(contextHubId, message.getMessageSequenceNumber(),
-                    errorCode);
+        if (!Flags.reliableMessageImplementation()
+                || !Flags.reliableMessageDuplicateDetectionService()) {
+            byte errorCode = mClientManager.onMessageFromNanoApp(contextHubId, hostEndpointId,
+                    message, nanoappPermissions, messagePermissions);
+            if (message.isReliable() && errorCode != ErrorCode.OK) {
+                sendMessageDeliveryStatusToContextHub(contextHubId,
+                        message.getMessageSequenceNumber(), errorCode);
+            }
+            return;
+        }
+
+        if (message.isReliable()) {
+            byte errorCode = ErrorCode.OK;
+            synchronized (mReliableMessageRecordQueue) {
+                Optional<ReliableMessageRecord> record = Optional.empty();
+                for (ReliableMessageRecord r: mReliableMessageRecordQueue) {
+                    if (r.getContextHubId() == contextHubId
+                            && r.getMessageSequenceNumber() == message.getMessageSequenceNumber()) {
+                        record = Optional.of(r);
+                        break;
+                    }
+                }
+
+                if (record.isPresent()) {
+                    errorCode = record.get().getErrorCode();
+                    if (errorCode == ErrorCode.TRANSIENT_ERROR) {
+                        Log.w(TAG, "Found duplicate reliable message with message sequence number: "
+                                + record.get().getMessageSequenceNumber() + ": retrying");
+                        errorCode = mClientManager.onMessageFromNanoApp(
+                                contextHubId, hostEndpointId, message,
+                                nanoappPermissions, messagePermissions);
+                        record.get().setErrorCode(errorCode);
+                    } else {
+                        Log.w(TAG, "Found duplicate reliable message with message sequence number: "
+                                + record.get().getMessageSequenceNumber());
+                    }
+                } else {
+                    errorCode = mClientManager.onMessageFromNanoApp(
+                            contextHubId, hostEndpointId, message,
+                            nanoappPermissions, messagePermissions);
+                    mReliableMessageRecordQueue.add(
+                            new ReliableMessageRecord(contextHubId,
+                                    SystemClock.elapsedRealtimeNanos(),
+                                    message.getMessageSequenceNumber(),
+                                    errorCode));
+                }
+            }
+            sendMessageDeliveryStatusToContextHub(contextHubId,
+                    message.getMessageSequenceNumber(), errorCode);
+        } else {
+            mClientManager.onMessageFromNanoApp(
+                    contextHubId, hostEndpointId, message,
+                    nanoappPermissions, messagePermissions);
+        }
+
+        synchronized (mReliableMessageRecordQueue) {
+            while (mReliableMessageRecordQueue.peek() != null
+                   && mReliableMessageRecordQueue.peek().isExpired()) {
+                mReliableMessageRecordQueue.poll();
+            }
         }
     }
 
+    /**
+     * Sends the message delivery status to the Context Hub.
+     *
+     * @param contextHubId the ID of the hub
+     * @param messageSequenceNumber the message sequence number
+     * @param errorCode the error code, one of the enum ErrorCode
+     */
     private void sendMessageDeliveryStatusToContextHub(int contextHubId,
             int messageSequenceNumber, byte errorCode) {
         if (!Flags.reliableMessageImplementation()) {
@@ -1229,6 +1424,9 @@
     public boolean setTestMode(boolean enable) {
         super.setTestMode_enforcePermission();
         boolean status = mContextHubWrapper.setTestMode(enable);
+        if (status) {
+            mIsTestModeEnabled.set(enable);
+        }
 
         // Query nanoapps to update service state after test mode state change.
         for (int contextHubId: mDefaultClientMap.keySet()) {
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
index a0dbfa0..ec94e2b 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
@@ -412,10 +412,15 @@
     /* package */
     synchronized void addTransaction(
             ContextHubServiceTransaction transaction) throws IllegalStateException {
+        if (transaction == null) {
+            return;
+        }
+
         if (mTransactionQueue.size() == MAX_PENDING_REQUESTS) {
             throw new IllegalStateException("Transaction queue is full (capacity = "
                     + MAX_PENDING_REQUESTS + ")");
         }
+
         mTransactionQueue.add(transaction);
         mTransactionRecordDeque.add(new TransactionRecord(transaction.toString()));
 
@@ -517,7 +522,10 @@
      * the caller has obtained a lock on this ContextHubTransactionManager object.
      */
     private void removeTransactionAndStartNext() {
-        mTimeoutFuture.cancel(false /* mayInterruptIfRunning */);
+        if (mTimeoutFuture != null) {
+            mTimeoutFuture.cancel(/* mayInterruptIfRunning= */ false);
+            mTimeoutFuture = null;
+        }
 
         ContextHubServiceTransaction transaction = mTransactionQueue.remove();
         transaction.setComplete();
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index 77a60289..bf1b3c3 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -168,6 +168,9 @@
     }
 
     private void syncKeys() throws RemoteException {
+        if (mCredentialUpdated && mRecoverableKeyStoreDb.getBadRemoteGuessCounter(mUserId) != 0) {
+            mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 0);
+        }
         int generation = mPlatformKeyManager.getGenerationId(mUserId);
         if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
             // Application keys for the user will not be available for sync.
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index a7fd750..386657e 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -81,6 +81,7 @@
 
     MediaRoute2ProviderServiceProxy(
             @NonNull Context context,
+            @NonNull Looper looper,
             @NonNull ComponentName componentName,
             boolean isSelfScanOnlyProvider,
             int userId) {
@@ -88,7 +89,7 @@
         mContext = Objects.requireNonNull(context, "Context must not be null.");
         mIsSelfScanOnlyProvider = isSelfScanOnlyProvider;
         mUserId = userId;
-        mHandler = new Handler(Looper.myLooper());
+        mHandler = new Handler(looper);
     }
 
     public void setManagerScanning(boolean managerScanning) {
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
index 178eb71..7c1a5e1 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
@@ -142,6 +142,7 @@
                     MediaRoute2ProviderServiceProxy proxy =
                             new MediaRoute2ProviderServiceProxy(
                                     mContext,
+                                    mHandler.getLooper(),
                                     new ComponentName(serviceInfo.packageName, serviceInfo.name),
                                     isSelfScanOnlyProvider,
                                     mUserId);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 869b89a..e1f8939 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -1305,22 +1305,20 @@
                         route.getId(),
                         requestId));
 
+        UserHandler userHandler = routerRecord.mUserRecord.mHandler;
         if (managerRequestId != MediaRoute2ProviderService.REQUEST_ID_NONE) {
-            ManagerRecord manager = routerRecord.mUserRecord.mHandler.findManagerWithId(
-                    toRequesterId(managerRequestId));
+            ManagerRecord manager = userHandler.findManagerWithId(toRequesterId(managerRequestId));
             if (manager == null || manager.mLastSessionCreationRequest == null) {
                 Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
                         + "Ignoring unknown request.");
-                routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
-                        routerRecord, requestId);
+                userHandler.notifySessionCreationFailedToRouter(routerRecord, requestId);
                 return;
             }
             if (!TextUtils.equals(manager.mLastSessionCreationRequest.mOldSession.getId(),
                     oldSession.getId())) {
                 Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
                         + "Ignoring unmatched routing session.");
-                routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
-                        routerRecord, requestId);
+                userHandler.notifySessionCreationFailedToRouter(routerRecord, requestId);
                 return;
             }
             if (!TextUtils.equals(manager.mLastSessionCreationRequest.mRoute.getId(),
@@ -1333,29 +1331,28 @@
                 } else {
                     Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
                             + "Ignoring unmatched route.");
-                    routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
-                            routerRecord, requestId);
+                    userHandler.notifySessionCreationFailedToRouter(routerRecord, requestId);
                     return;
                 }
             }
             manager.mLastSessionCreationRequest = null;
         } else {
+            String defaultRouteId = userHandler.mSystemProvider.getDefaultRoute().getId();
             if (route.isSystemRoute()
                     && !routerRecord.hasSystemRoutingPermission()
-                    && !TextUtils.equals(route.getId(), MediaRoute2Info.ROUTE_ID_DEFAULT)) {
+                    && !TextUtils.equals(route.getId(), defaultRouteId)) {
                 Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to"
                         + route);
-                routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
-                        routerRecord, requestId);
+                userHandler.notifySessionCreationFailedToRouter(routerRecord, requestId);
                 return;
             }
         }
 
         long uniqueRequestId = toUniqueRequestId(routerRecord.mRouterId, requestId);
-        routerRecord.mUserRecord.mHandler.sendMessage(
+        userHandler.sendMessage(
                 obtainMessage(
                         UserHandler::requestCreateSessionWithRouter2OnHandler,
-                        routerRecord.mUserRecord.mHandler,
+                        userHandler,
                         uniqueRequestId,
                         managerRequestId,
                         transferInitiatorUserHandle,
@@ -1429,18 +1426,22 @@
                         "transferToRouteWithRouter2 | router: %s(id: %d), route: %s",
                         routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
 
+        UserHandler userHandler = routerRecord.mUserRecord.mHandler;
+        String defaultRouteId = userHandler.mSystemProvider.getDefaultRoute().getId();
         if (route.isSystemRoute()
                 && !routerRecord.hasSystemRoutingPermission()
-                && !TextUtils.equals(route.getId(), MediaRoute2Info.ROUTE_ID_DEFAULT)) {
-            routerRecord.mUserRecord.mHandler.sendMessage(
-                    obtainMessage(UserHandler::notifySessionCreationFailedToRouter,
-                            routerRecord.mUserRecord.mHandler,
-                            routerRecord, toOriginalRequestId(DUMMY_REQUEST_ID)));
+                && !TextUtils.equals(route.getId(), defaultRouteId)) {
+            userHandler.sendMessage(
+                    obtainMessage(
+                            UserHandler::notifySessionCreationFailedToRouter,
+                            userHandler,
+                            routerRecord,
+                            toOriginalRequestId(DUMMY_REQUEST_ID)));
         } else {
-            routerRecord.mUserRecord.mHandler.sendMessage(
+            userHandler.sendMessage(
                     obtainMessage(
                             UserHandler::transferToRouteOnHandler,
-                            routerRecord.mUserRecord.mHandler,
+                            userHandler,
                             DUMMY_REQUEST_ID,
                             transferInitiatorUserHandle,
                             routerRecord.mPackageName,
@@ -2799,6 +2800,10 @@
             final String providerId = route.getProviderId();
             final MediaRoute2Provider provider = findProvider(providerId);
             if (provider == null) {
+                Slog.w(
+                        TAG,
+                        "Ignoring transferToRoute due to lack of matching provider for target: "
+                                + route);
                 return;
             }
             provider.transferToRoute(
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index a3c5d2d..69f07d5 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -1057,6 +1057,7 @@
         return -1;
     }
 
+    @NonNull
     private PlaybackInfo getVolumeAttributes() {
         int volumeType;
         AudioAttributes attributes;
@@ -1850,6 +1851,7 @@
             return mFlags;
         }
 
+        @NonNull
         @Override
         public PlaybackInfo getVolumeAttributes() {
             return MediaSessionRecord.this.getVolumeAttributes();
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index c105b9c..6ce3ab4 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -232,10 +232,16 @@
             String sessionId,
             String routeId,
             @RoutingSessionInfo.TransferReason int transferReason) {
+        String selectedDeviceRouteId = mDeviceRouteController.getSelectedRoute().getId();
         if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
-            // The currently selected route is the default route.
-            Log.w(TAG, "Ignoring transfer to " + MediaRoute2Info.ROUTE_ID_DEFAULT);
-            return;
+            if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
+                // Transfer to the default route (which is the selected route). We replace the id to
+                // be the selected route id so that the transfer reason gets updated.
+                routeId = selectedDeviceRouteId;
+            } else {
+                Log.w(TAG, "Ignoring transfer to " + MediaRoute2Info.ROUTE_ID_DEFAULT);
+                return;
+            }
         }
 
         if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
@@ -250,11 +256,11 @@
             }
         }
 
-        MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
+        String finalRouteId = routeId; // Make a final copy to use it in the lambda.
         boolean isAvailableDeviceRoute =
                 mDeviceRouteController.getAvailableRoutes().stream()
-                        .anyMatch(it -> it.getId().equals(routeId));
-        boolean isSelectedDeviceRoute = TextUtils.equals(routeId, selectedDeviceRoute.getId());
+                        .anyMatch(it -> it.getId().equals(finalRouteId));
+        boolean isSelectedDeviceRoute = TextUtils.equals(routeId, selectedDeviceRouteId);
 
         if (isSelectedDeviceRoute || isAvailableDeviceRoute) {
             // The requested route is managed by the device route controller. Note that the selected
diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java
index d25f529..5ea3e70 100644
--- a/services/core/java/com/android/server/net/NetworkManagementService.java
+++ b/services/core/java/com/android/server/net/NetworkManagementService.java
@@ -20,6 +20,9 @@
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
@@ -31,6 +34,9 @@
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_BACKGROUND;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_ALLOW;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_DENY_USER;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_RESTRICTED;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
@@ -143,6 +149,8 @@
     private final Object mQuotaLock = new Object();
     private final Object mRulesLock = new Object();
 
+    private final boolean mUseMeteredFirewallChains;
+
     /** Set of interfaces with active quotas. */
     @GuardedBy("mQuotaLock")
     private HashMap<String, Long> mActiveQuotas = Maps.newHashMap();
@@ -150,9 +158,11 @@
     @GuardedBy("mQuotaLock")
     private HashMap<String, Long> mActiveAlerts = Maps.newHashMap();
     /** Set of UIDs denied on metered networks. */
+    // TODO: b/336693007 - Remove once NPMS has completely migrated to metered firewall chains.
     @GuardedBy("mRulesLock")
     private SparseBooleanArray mUidRejectOnMetered = new SparseBooleanArray();
     /** Set of UIDs allowed on metered networks. */
+    // TODO: b/336693007 - Remove once NPMS has completely migrated to metered firewall chains.
     @GuardedBy("mRulesLock")
     private SparseBooleanArray mUidAllowOnMetered = new SparseBooleanArray();
     /** Set of UIDs with cleartext penalties. */
@@ -196,10 +206,32 @@
     @GuardedBy("mRulesLock")
     private final SparseIntArray mUidFirewallBackgroundRules = new SparseIntArray();
 
+    /**
+     * Contains the per-UID firewall rules that are used to allowlist the app from metered-network
+     * restrictions when data saver is enabled.
+     */
+    @GuardedBy("mRulesLock")
+    private final SparseIntArray mUidMeteredFirewallAllowRules = new SparseIntArray();
+
+    /**
+     * Contains the per-UID firewall rules that are used to deny app access to metered networks
+     * due to user action.
+     */
+    @GuardedBy("mRulesLock")
+    private final SparseIntArray mUidMeteredFirewallDenyUserRules = new SparseIntArray();
+
+    /**
+     * Contains the per-UID firewall rules that are used to deny app access to metered networks
+     * due to admin action.
+     */
+    @GuardedBy("mRulesLock")
+    private final SparseIntArray mUidMeteredFirewallDenyAdminRules = new SparseIntArray();
+
     /** Set of states for the child firewall chains. True if the chain is active. */
     @GuardedBy("mRulesLock")
     final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray();
 
+    // TODO: b/336693007 - Remove once NPMS has completely migrated to metered firewall chains.
     @GuardedBy("mQuotaLock")
     private volatile boolean mDataSaverMode;
 
@@ -217,6 +249,15 @@
         mContext = context;
         mDeps = deps;
 
+        mUseMeteredFirewallChains = Flags.useMeteredFirewallChains();
+
+        if (mUseMeteredFirewallChains) {
+            // These firewalls are always on and currently ConnectivityService does not allow
+            // changing their enabled state.
+            mFirewallChainStates.put(FIREWALL_CHAIN_METERED_DENY_USER, true);
+            mFirewallChainStates.put(FIREWALL_CHAIN_METERED_DENY_ADMIN, true);
+        }
+
         mDaemonHandler = new Handler(FgThread.get().getLooper());
 
         mNetdUnsolicitedEventListener = new NetdUnsolicitedEventListener();
@@ -410,33 +451,39 @@
                 }
             }
 
-            SparseBooleanArray uidRejectOnQuota = null;
-            SparseBooleanArray uidAcceptOnQuota = null;
-            synchronized (mRulesLock) {
-                size = mUidRejectOnMetered.size();
-                if (size > 0) {
-                    if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered denylist rules");
-                    uidRejectOnQuota = mUidRejectOnMetered;
-                    mUidRejectOnMetered = new SparseBooleanArray();
-                }
+            if (!mUseMeteredFirewallChains) {
+                SparseBooleanArray uidRejectOnQuota = null;
+                SparseBooleanArray uidAcceptOnQuota = null;
+                synchronized (mRulesLock) {
+                    size = mUidRejectOnMetered.size();
+                    if (size > 0) {
+                        if (DBG) {
+                            Slog.d(TAG, "Pushing " + size + " UIDs to metered denylist rules");
+                        }
+                        uidRejectOnQuota = mUidRejectOnMetered;
+                        mUidRejectOnMetered = new SparseBooleanArray();
+                    }
 
-                size = mUidAllowOnMetered.size();
-                if (size > 0) {
-                    if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered allowlist rules");
-                    uidAcceptOnQuota = mUidAllowOnMetered;
-                    mUidAllowOnMetered = new SparseBooleanArray();
+                    size = mUidAllowOnMetered.size();
+                    if (size > 0) {
+                        if (DBG) {
+                            Slog.d(TAG, "Pushing " + size + " UIDs to metered allowlist rules");
+                        }
+                        uidAcceptOnQuota = mUidAllowOnMetered;
+                        mUidAllowOnMetered = new SparseBooleanArray();
+                    }
                 }
-            }
-            if (uidRejectOnQuota != null) {
-                for (int i = 0; i < uidRejectOnQuota.size(); i++) {
-                    setUidOnMeteredNetworkDenylist(uidRejectOnQuota.keyAt(i),
-                            uidRejectOnQuota.valueAt(i));
+                if (uidRejectOnQuota != null) {
+                    for (int i = 0; i < uidRejectOnQuota.size(); i++) {
+                        setUidOnMeteredNetworkDenylist(uidRejectOnQuota.keyAt(i),
+                                uidRejectOnQuota.valueAt(i));
+                    }
                 }
-            }
-            if (uidAcceptOnQuota != null) {
-                for (int i = 0; i < uidAcceptOnQuota.size(); i++) {
-                    setUidOnMeteredNetworkAllowlist(uidAcceptOnQuota.keyAt(i),
-                            uidAcceptOnQuota.valueAt(i));
+                if (uidAcceptOnQuota != null) {
+                    for (int i = 0; i < uidAcceptOnQuota.size(); i++) {
+                        setUidOnMeteredNetworkAllowlist(uidAcceptOnQuota.keyAt(i),
+                                uidAcceptOnQuota.valueAt(i));
+                    }
                 }
             }
 
@@ -459,8 +506,16 @@
             syncFirewallChainLocked(FIREWALL_CHAIN_RESTRICTED, "restricted ");
             syncFirewallChainLocked(FIREWALL_CHAIN_LOW_POWER_STANDBY, "low power standby ");
             syncFirewallChainLocked(FIREWALL_CHAIN_BACKGROUND, FIREWALL_CHAIN_NAME_BACKGROUND);
+            if (mUseMeteredFirewallChains) {
+                syncFirewallChainLocked(FIREWALL_CHAIN_METERED_ALLOW,
+                        FIREWALL_CHAIN_NAME_METERED_ALLOW);
+                syncFirewallChainLocked(FIREWALL_CHAIN_METERED_DENY_USER,
+                        FIREWALL_CHAIN_NAME_METERED_DENY_USER);
+                syncFirewallChainLocked(FIREWALL_CHAIN_METERED_DENY_ADMIN,
+                        FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN);
+            }
 
-            final int[] chains = {
+            final int[] chainsToEnable = {
                     FIREWALL_CHAIN_STANDBY,
                     FIREWALL_CHAIN_DOZABLE,
                     FIREWALL_CHAIN_POWERSAVE,
@@ -469,14 +524,13 @@
                     FIREWALL_CHAIN_BACKGROUND,
             };
 
-            for (int chain : chains) {
+            for (int chain : chainsToEnable) {
                 if (getFirewallChainState(chain)) {
                     setFirewallChainEnabled(chain, true);
                 }
             }
         }
 
-
         try {
             getBatteryStats().noteNetworkStatsEnabled();
         } catch (RemoteException e) {
@@ -1077,6 +1131,14 @@
                     mContext.getSystemService(ConnectivityManager.class)
                             .setDataSaverEnabled(enable);
                     mDataSaverMode = enable;
+                    if (mUseMeteredFirewallChains) {
+                        // Copy mDataSaverMode state to FIREWALL_CHAIN_METERED_ALLOW
+                        // until ConnectivityService allows manipulation of the data saver mode via
+                        // FIREWALL_CHAIN_METERED_ALLOW.
+                        synchronized (mRulesLock) {
+                            mFirewallChainStates.put(FIREWALL_CHAIN_METERED_ALLOW, enable);
+                        }
+                    }
                     return true;
                 } else {
                     final boolean changed = mNetdService.bandwidthEnableDataSaver(enable);
@@ -1191,9 +1253,9 @@
                 setFirewallChainState(chain, enable);
             }
 
-            final String chainName = getFirewallChainName(chain);
-            if (chain == FIREWALL_CHAIN_NONE) {
-                throw new IllegalArgumentException("Bad child chain: " + chainName);
+            if (!isValidFirewallChainForSetEnabled(chain)) {
+                throw new IllegalArgumentException("Invalid chain for setFirewallChainEnabled: "
+                        + NetworkPolicyLogger.getFirewallChainName(chain));
             }
 
             final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
@@ -1205,38 +1267,29 @@
         }
     }
 
-    private String getFirewallChainName(int chain) {
-        switch (chain) {
-            case FIREWALL_CHAIN_STANDBY:
-                return FIREWALL_CHAIN_NAME_STANDBY;
-            case FIREWALL_CHAIN_DOZABLE:
-                return FIREWALL_CHAIN_NAME_DOZABLE;
-            case FIREWALL_CHAIN_POWERSAVE:
-                return FIREWALL_CHAIN_NAME_POWERSAVE;
-            case FIREWALL_CHAIN_RESTRICTED:
-                return FIREWALL_CHAIN_NAME_RESTRICTED;
-            case FIREWALL_CHAIN_LOW_POWER_STANDBY:
-                return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
-            case FIREWALL_CHAIN_BACKGROUND:
-                return FIREWALL_CHAIN_NAME_BACKGROUND;
-            default:
-                throw new IllegalArgumentException("Bad child chain: " + chain);
-        }
+    private boolean isValidFirewallChainForSetEnabled(int chain) {
+        return switch (chain) {
+            case FIREWALL_CHAIN_STANDBY, FIREWALL_CHAIN_DOZABLE, FIREWALL_CHAIN_POWERSAVE,
+                    FIREWALL_CHAIN_RESTRICTED, FIREWALL_CHAIN_LOW_POWER_STANDBY,
+                    FIREWALL_CHAIN_BACKGROUND -> true;
+            // METERED_* firewall chains are not yet supported by
+            // ConnectivityService#setFirewallChainEnabled.
+            default -> false;
+        };
     }
 
     private int getFirewallType(int chain) {
         switch (chain) {
             case FIREWALL_CHAIN_STANDBY:
+            case FIREWALL_CHAIN_METERED_DENY_ADMIN:
+            case FIREWALL_CHAIN_METERED_DENY_USER:
                 return FIREWALL_DENYLIST;
             case FIREWALL_CHAIN_DOZABLE:
-                return FIREWALL_ALLOWLIST;
             case FIREWALL_CHAIN_POWERSAVE:
-                return FIREWALL_ALLOWLIST;
             case FIREWALL_CHAIN_RESTRICTED:
-                return FIREWALL_ALLOWLIST;
             case FIREWALL_CHAIN_LOW_POWER_STANDBY:
-                return FIREWALL_ALLOWLIST;
             case FIREWALL_CHAIN_BACKGROUND:
+            case FIREWALL_CHAIN_METERED_ALLOW:
                 return FIREWALL_ALLOWLIST;
             default:
                 return isFirewallEnabled() ? FIREWALL_ALLOWLIST : FIREWALL_DENYLIST;
@@ -1360,6 +1413,12 @@
                 return mUidFirewallLowPowerStandbyRules;
             case FIREWALL_CHAIN_BACKGROUND:
                 return mUidFirewallBackgroundRules;
+            case FIREWALL_CHAIN_METERED_ALLOW:
+                return mUidMeteredFirewallAllowRules;
+            case FIREWALL_CHAIN_METERED_DENY_USER:
+                return mUidMeteredFirewallDenyUserRules;
+            case FIREWALL_CHAIN_METERED_DENY_ADMIN:
+                return mUidMeteredFirewallDenyAdminRules;
             case FIREWALL_CHAIN_NONE:
                 return mUidFirewallRules;
             default:
@@ -1378,6 +1437,10 @@
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
+        pw.println("Flags:");
+        pw.println(Flags.FLAG_USE_METERED_FIREWALL_CHAINS + ": " + mUseMeteredFirewallChains);
+        pw.println();
+
         synchronized (mQuotaLock) {
             pw.print("Active quota ifaces: "); pw.println(mActiveQuotas.toString());
             pw.print("Active alert ifaces: "); pw.println(mActiveAlerts.toString());
@@ -1416,6 +1479,27 @@
             pw.print("UID firewall background chain enabled: ");
             pw.println(getFirewallChainState(FIREWALL_CHAIN_BACKGROUND));
             dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_BACKGROUND, mUidFirewallBackgroundRules);
+
+            pw.print("UID firewall metered allow chain enabled (Data saver mode): ");
+            // getFirewallChainState should maintain a duplicated state from mDataSaverMode when
+            // mUseMeteredFirewallChains is enabled.
+            pw.println(getFirewallChainState(FIREWALL_CHAIN_METERED_ALLOW));
+            dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_METERED_ALLOW,
+                    mUidMeteredFirewallAllowRules);
+
+            pw.print("UID firewall metered deny_user chain enabled (always-on): ");
+            // This always-on state should be reflected by getFirewallChainState when
+            // mUseMeteredFirewallChains is enabled.
+            pw.println(getFirewallChainState(FIREWALL_CHAIN_METERED_DENY_USER));
+            dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_METERED_DENY_USER,
+                    mUidMeteredFirewallDenyUserRules);
+
+            pw.print("UID firewall metered deny_admin chain enabled (always-on): ");
+            // This always-on state should be reflected by getFirewallChainState when
+            // mUseMeteredFirewallChains is enabled.
+            pw.println(getFirewallChainState(FIREWALL_CHAIN_METERED_DENY_ADMIN));
+            dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN,
+                    mUidMeteredFirewallDenyAdminRules);
         }
 
         pw.print("Firewall enabled: "); pw.println(mFirewallEnabled);
@@ -1520,14 +1604,40 @@
                 if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because it is in background");
                 return true;
             }
-            if (mUidRejectOnMetered.get(uid)) {
-                if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of no metered data"
-                        + " in the background");
-                return true;
-            }
-            if (mDataSaverMode && !mUidAllowOnMetered.get(uid)) {
-                if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of data saver mode");
-                return true;
+            if (mUseMeteredFirewallChains) {
+                if (getFirewallChainState(FIREWALL_CHAIN_METERED_DENY_USER)
+                        && mUidMeteredFirewallDenyUserRules.get(uid) == FIREWALL_RULE_DENY) {
+                    if (DBG) {
+                        Slog.d(TAG, "Uid " + uid + " restricted because of user-restricted metered"
+                                + " data in the background");
+                    }
+                    return true;
+                }
+                if (getFirewallChainState(FIREWALL_CHAIN_METERED_DENY_ADMIN)
+                        && mUidMeteredFirewallDenyAdminRules.get(uid) == FIREWALL_RULE_DENY) {
+                    if (DBG) {
+                        Slog.d(TAG, "Uid " + uid + " restricted because of admin-restricted metered"
+                                + " data in the background");
+                    }
+                    return true;
+                }
+                if (getFirewallChainState(FIREWALL_CHAIN_METERED_ALLOW)
+                        && mUidMeteredFirewallAllowRules.get(uid) != FIREWALL_RULE_ALLOW) {
+                    if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of data saver mode");
+                    return true;
+                }
+            } else {
+                if (mUidRejectOnMetered.get(uid)) {
+                    if (DBG) {
+                        Slog.d(TAG, "Uid " + uid
+                                + " restricted because of no metered data in the background");
+                    }
+                    return true;
+                }
+                if (mDataSaverMode && !mUidAllowOnMetered.get(uid)) {
+                    if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of data saver mode");
+                    return true;
+                }
             }
             return false;
         }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index 8e2d778..681aa8a 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -19,6 +19,9 @@
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
@@ -28,6 +31,9 @@
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_BACKGROUND;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_ALLOW;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_DENY_USER;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_RESTRICTED;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
@@ -379,7 +385,7 @@
         return "Interfaces of netId=" + netId + " changed to " + newIfaces;
     }
 
-    private static String getFirewallChainName(int chain) {
+    static String getFirewallChainName(int chain) {
         switch (chain) {
             case FIREWALL_CHAIN_DOZABLE:
                 return FIREWALL_CHAIN_NAME_DOZABLE;
@@ -393,6 +399,12 @@
                 return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
             case FIREWALL_CHAIN_BACKGROUND:
                 return FIREWALL_CHAIN_NAME_BACKGROUND;
+            case FIREWALL_CHAIN_METERED_ALLOW:
+                return FIREWALL_CHAIN_NAME_METERED_ALLOW;
+            case FIREWALL_CHAIN_METERED_DENY_USER:
+                return FIREWALL_CHAIN_NAME_METERED_DENY_USER;
+            case FIREWALL_CHAIN_METERED_DENY_ADMIN:
+                return FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN;
             default:
                 return String.valueOf(chain);
         }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 22f5332..c60ac3a 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -60,6 +60,9 @@
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
@@ -514,6 +517,12 @@
      */
     private boolean mBackgroundNetworkRestricted;
 
+    /**
+     * Whether or not metered firewall chains should be used for uid policy controlling access to
+     * metered networks.
+     */
+    private boolean mUseMeteredFirewallChains;
+
     // See main javadoc for instructions on how to use these locks.
     final Object mUidRulesFirstLock = new Object();
     final Object mNetworkPoliciesSecondLock = new Object();
@@ -997,6 +1006,8 @@
             mAppStandby = LocalServices.getService(AppStandbyInternal.class);
             mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
 
+            mUseMeteredFirewallChains = Flags.useMeteredFirewallChains();
+
             synchronized (mUidRulesFirstLock) {
                 synchronized (mNetworkPoliciesSecondLock) {
                     updatePowerSaveAllowlistUL();
@@ -4030,8 +4041,10 @@
 
                 fout.println();
                 fout.println("Flags:");
-                fout.println("Network blocked for TOP_SLEEPING and above: "
+                fout.println(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE + ": "
                         + mBackgroundNetworkRestricted);
+                fout.println(Flags.FLAG_USE_METERED_FIREWALL_CHAINS + ": "
+                        + mUseMeteredFirewallChains);
 
                 fout.println();
                 fout.println("mRestrictBackgroundLowPowerMode: " + mRestrictBackgroundLowPowerMode);
@@ -5373,23 +5386,44 @@
             postUidRulesChangedMsg(uid, uidRules);
         }
 
-        // Note that the conditionals below are for avoiding unnecessary calls to netd.
-        // TODO: Measure the performance for doing a no-op call to netd so that we can
-        // remove the conditionals to simplify the logic below. We can also further reduce
-        // some calls to netd if they turn out to be costly.
-        final int denylistReasons = BLOCKED_METERED_REASON_ADMIN_DISABLED
-                | BLOCKED_METERED_REASON_USER_RESTRICTED;
-        if ((oldEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE
-                || (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE) {
-            setMeteredNetworkDenylist(uid,
-                    (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE);
-        }
-        final int allowlistReasons = ALLOWED_METERED_REASON_FOREGROUND
-                | ALLOWED_METERED_REASON_USER_EXEMPTED;
-        if ((oldAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE
-                || (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE) {
-            setMeteredNetworkAllowlist(uid,
-                    (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE);
+        if (mUseMeteredFirewallChains) {
+            if ((newEffectiveBlockedReasons & BLOCKED_METERED_REASON_ADMIN_DISABLED)
+                    != BLOCKED_REASON_NONE) {
+                setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_DENY_ADMIN, uid, FIREWALL_RULE_DENY);
+            } else {
+                setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_DENY_ADMIN, uid, FIREWALL_RULE_DEFAULT);
+            }
+            if ((newEffectiveBlockedReasons & BLOCKED_METERED_REASON_USER_RESTRICTED)
+                    != BLOCKED_REASON_NONE) {
+                setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_DENY_USER, uid, FIREWALL_RULE_DENY);
+            } else {
+                setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_DENY_USER, uid, FIREWALL_RULE_DEFAULT);
+            }
+            if ((newAllowedReasons & (ALLOWED_METERED_REASON_FOREGROUND
+                    | ALLOWED_METERED_REASON_USER_EXEMPTED)) != ALLOWED_REASON_NONE) {
+                setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_ALLOW, uid, FIREWALL_RULE_ALLOW);
+            } else {
+                setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_ALLOW, uid, FIREWALL_RULE_DEFAULT);
+            }
+        } else {
+            // Note that the conditionals below are for avoiding unnecessary calls to netd.
+            // TODO: Measure the performance for doing a no-op call to netd so that we can
+            // remove the conditionals to simplify the logic below. We can also further reduce
+            // some calls to netd if they turn out to be costly.
+            final int denylistReasons = BLOCKED_METERED_REASON_ADMIN_DISABLED
+                    | BLOCKED_METERED_REASON_USER_RESTRICTED;
+            if ((oldEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE
+                    || (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE) {
+                setMeteredNetworkDenylist(uid,
+                        (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE);
+            }
+            final int allowlistReasons = ALLOWED_METERED_REASON_FOREGROUND
+                    | ALLOWED_METERED_REASON_USER_EXEMPTED;
+            if ((oldAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE
+                    || (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE) {
+                setMeteredNetworkAllowlist(uid,
+                        (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE);
+            }
         }
     }
 
@@ -6149,6 +6183,8 @@
             } else if (chain == FIREWALL_CHAIN_BACKGROUND) {
                 mUidFirewallBackgroundRules.put(uid, rule);
             }
+            // Note that we do not need keep a separate cache of uid rules for chains that we do
+            // not call #setUidFirewallRulesUL for.
 
             try {
                 mNetworkManager.setFirewallUidRule(chain, uid, rule);
@@ -6206,10 +6242,19 @@
                     FIREWALL_RULE_DEFAULT);
             mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, uid,
                     FIREWALL_RULE_DEFAULT);
-            mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, false);
-            mLogger.meteredAllowlistChanged(uid, false);
-            mNetworkManager.setUidOnMeteredNetworkDenylist(uid, false);
-            mLogger.meteredDenylistChanged(uid, false);
+            if (mUseMeteredFirewallChains) {
+                mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, uid,
+                        FIREWALL_RULE_DEFAULT);
+                mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_USER, uid,
+                        FIREWALL_RULE_DEFAULT);
+                mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_METERED_ALLOW, uid,
+                        FIREWALL_RULE_DEFAULT);
+            } else {
+                mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, false);
+                mLogger.meteredAllowlistChanged(uid, false);
+                mNetworkManager.setUidOnMeteredNetworkDenylist(uid, false);
+                mLogger.meteredDenylistChanged(uid, false);
+            }
         } catch (IllegalStateException e) {
             Log.wtf(TAG, "problem resetting firewall uid rules for " + uid, e);
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/net/flags.aconfig b/services/core/java/com/android/server/net/flags.aconfig
index d9491de..e986dd8 100644
--- a/services/core/java/com/android/server/net/flags.aconfig
+++ b/services/core/java/com/android/server/net/flags.aconfig
@@ -7,3 +7,13 @@
     description: "Block network access for apps in a low importance background state"
     bug: "304347838"
 }
+
+flag {
+    name: "use_metered_firewall_chains"
+    namespace: "backstage_power"
+    description: "Use metered firewall chains to control access to metered networks"
+    bug: "336693007"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index bf49671..13429db 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -22,12 +22,14 @@
 import static android.app.NotificationManager.IMPORTANCE_MIN;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
 import static android.media.audio.Flags.focusExclusiveWithRecording;
 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS;
 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
 
+import android.Manifest.permission;
 import android.annotation.IntDef;
 import android.app.ActivityManager;
 import android.app.KeyguardManager;
@@ -135,7 +137,7 @@
     private LogicalLight mAttentionLight;
 
     private final boolean mUseAttentionLight;
-    boolean mHasLight = true;
+    boolean mHasLight;
 
     private final SettingsObserver mSettingsObserver;
 
@@ -149,7 +151,7 @@
     private boolean mInCallStateOffHook = false;
     private boolean mScreenOn = true;
     private boolean mUserPresent = false;
-    boolean mNotificationPulseEnabled;
+    private boolean mNotificationPulseEnabled;
     private final Uri mInCallNotificationUri;
     private final AudioAttributes mInCallNotificationAudioAttributes;
     private final float mInCallNotificationVolume;
@@ -223,7 +225,10 @@
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
-                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET));
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET),
+                    record -> mPackageManager.checkPermission(
+                            permission.RECEIVE_EMERGENCY_BROADCAST,
+                            record.getSbn().getPackageName()) == PERMISSION_GRANTED);
 
             return new StrategyAvalanche(
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
@@ -231,14 +236,17 @@
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_AVALANCHE_TIMEOUT),
-                    appStrategy);
+                    appStrategy, appStrategy.mExemptionProvider);
         } else {
             return new StrategyPerApp(
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
-                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET));
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET),
+                    record -> mPackageManager.checkPermission(
+                            permission.RECEIVE_EMERGENCY_BROADCAST,
+                            record.getSbn().getPackageName()) == PERMISSION_GRANTED);
         }
     }
 
@@ -305,6 +313,13 @@
     }
 
     private void loadUserSettings() {
+        boolean pulseEnabled = Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT) != 0;
+        if (mNotificationPulseEnabled != pulseEnabled) {
+            mNotificationPulseEnabled = pulseEnabled;
+            updateLightsLocked();
+        }
+
         if (Flags.politeNotifications()) {
             try {
                 mCurrentWorkProfileId = getManagedProfileId(ActivityManager.getCurrentUser());
@@ -874,6 +889,9 @@
 
     boolean canShowLightsLocked(final NotificationRecord record, final Signals signals,
             boolean aboveThreshold) {
+        if (!mSystemReady) {
+            return false;
+        }
         // device lacks light
         if (!mHasLight) {
             return false;
@@ -1088,6 +1106,11 @@
         }
     }
 
+    // Returns true if a notification should be exempted from attenuation
+    private interface ExemptionProvider {
+        boolean isExempted(NotificationRecord record);
+    }
+
     @VisibleForTesting
     abstract static class PolitenessStrategy {
         static final int POLITE_STATE_DEFAULT = 0;
@@ -1118,8 +1141,10 @@
 
         protected boolean mIsActive = true;
 
+        protected final ExemptionProvider mExemptionProvider;
+
         public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite,
-                int volumeMuted) {
+                int volumeMuted, ExemptionProvider exemptionProvider) {
             mVolumeStates = new HashMap<>();
             mLastUpdatedTimestampByPackage = new HashMap<>();
 
@@ -1127,6 +1152,7 @@
             this.mTimeoutMuted = timeoutMuted;
             this.mVolumePolite = volumePolite / 100.0f;
             this.mVolumeMuted = volumeMuted / 100.0f;
+            this.mExemptionProvider = exemptionProvider;
         }
 
         abstract void onNotificationPosted(NotificationRecord record);
@@ -1284,8 +1310,8 @@
         private final int mMaxPostedForReset;
 
         public StrategyPerApp(int timeoutPolite, int timeoutMuted, int volumePolite,
-                int volumeMuted, int maxPosted) {
-            super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
+                int volumeMuted, int maxPosted, ExemptionProvider exemptionProvider) {
+            super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted, exemptionProvider);
 
             mNumPosted = new HashMap<>();
             mMaxPostedForReset = maxPosted;
@@ -1306,7 +1332,12 @@
 
             final String key = getChannelKey(record);
             @PolitenessState final int currState = getPolitenessState(record);
-            @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
+            @PolitenessState int nextState;
+            if (Flags.politeNotificationsAttnUpdate()) {
+                nextState = getNextState(currState, timeSinceLastNotif, record);
+            } else {
+                nextState = getNextState(currState, timeSinceLastNotif);
+            }
 
             // Reset to default state if number of posted notifications exceed this value when muted
             int numPosted = mNumPosted.getOrDefault(key, 0) + 1;
@@ -1324,6 +1355,14 @@
             mVolumeStates.put(key, nextState);
         }
 
+        @PolitenessState int getNextState(@PolitenessState final int currState,
+                final long timeSinceLastNotif, final NotificationRecord record) {
+            if (mExemptionProvider.isExempted(record)) {
+                return POLITE_STATE_DEFAULT;
+            }
+            return getNextState(currState, timeSinceLastNotif);
+        }
+
         @Override
         public void onUserInteraction(final NotificationRecord record) {
             super.onUserInteraction(record);
@@ -1344,8 +1383,9 @@
         private long mLastAvalancheTriggerTimestamp = 0;
 
         StrategyAvalanche(int timeoutPolite, int timeoutMuted, int volumePolite,
-                    int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy) {
-            super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
+                    int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy,
+                    ExemptionProvider exemptionProvider) {
+            super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted, exemptionProvider);
 
             mTimeoutAvalanche = timeoutAvalanche;
             mAppStrategy = appStrategy;
@@ -1518,7 +1558,7 @@
                 return true;
             }
 
-            return false;
+            return mExemptionProvider.isExempted(record);
         }
 
         private boolean isAvalancheExempted(final NotificationRecord record) {
@@ -1721,8 +1761,6 @@
     void setLights(LogicalLight light) {
         mNotificationLight = light;
         mAttentionLight = light;
-        mNotificationPulseEnabled = true;
-        mHasLight = true;
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3dd2f1e..44e7694 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -245,7 +245,6 @@
 import android.os.DeviceIdleManager;
 import android.os.Environment;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.IInterface;
@@ -594,6 +593,8 @@
 
     static final long NOTIFICATION_TTL = Duration.ofDays(3).toMillis();
 
+    static final long NOTIFICATION_MAX_AGE_AT_POST = Duration.ofDays(14).toMillis();
+
     private IActivityManager mAm;
     private ActivityTaskManagerInternal mAtm;
     private ActivityManager mActivityManager;
@@ -2038,19 +2039,21 @@
                     mSnoozeHelper.clearData(userHandle);
                 }
             } else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
-                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
-                mUserProfiles.updateCache(context);
-                if (!mUserProfiles.isProfileUser(userId, context)) {
-                    // reload per-user settings
-                    mSettingsObserver.update(null);
-                    // Refresh managed services
-                    mConditionProviders.onUserSwitched(userId);
-                    mListeners.onUserSwitched(userId);
-                    mZenModeHelper.onUserSwitched(userId);
-                    mPreferencesHelper.syncChannelsBypassingDnd();
+                if (!Flags.useSsmUserSwitchSignal()) {
+                    final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
+                    mUserProfiles.updateCache(context);
+                    if (!mUserProfiles.isProfileUser(userId, context)) {
+                        // reload per-user settings
+                        mSettingsObserver.update(null);
+                        // Refresh managed services
+                        mConditionProviders.onUserSwitched(userId);
+                        mListeners.onUserSwitched(userId);
+                        mZenModeHelper.onUserSwitched(userId);
+                        mPreferencesHelper.syncChannelsBypassingDnd();
+                    }
+                    // assistant is the only thing that cares about managed profiles specifically
+                    mAssistants.onUserSwitched(userId);
                 }
-                // assistant is the only thing that cares about managed profiles specifically
-                mAssistants.onUserSwitched(userId);
             } else if (action.equals(Intent.ACTION_USER_ADDED)) {
                 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
                 if (userId != USER_NULL) {
@@ -2570,7 +2573,9 @@
         // calling onDestroy()
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_STOPPED);
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
+        if (!Flags.useSsmUserSwitchSignal()) {
+            filter.addAction(Intent.ACTION_USER_SWITCHED);
+        }
         filter.addAction(Intent.ACTION_USER_ADDED);
         filter.addAction(Intent.ACTION_USER_REMOVED);
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
@@ -2634,27 +2639,48 @@
      * Cleanup broadcast receivers change listeners.
      */
     public void onDestroy() {
-        getContext().unregisterReceiver(mIntentReceiver);
-        getContext().unregisterReceiver(mPackageIntentReceiver);
-        if (Flags.allNotifsNeedTtl()) {
-            mTtlHelper.destroy();
-        } else {
-            getContext().unregisterReceiver(mNotificationTimeoutReceiver);
+        if (mIntentReceiver != null) {
+            getContext().unregisterReceiver(mIntentReceiver);
         }
-        getContext().unregisterReceiver(mRestoreReceiver);
-        getContext().unregisterReceiver(mLocaleChangeReceiver);
-
-        mSettingsObserver.destroy();
-        mRoleObserver.destroy();
+        if (mPackageIntentReceiver != null) {
+            getContext().unregisterReceiver(mPackageIntentReceiver);
+        }
+        if (Flags.allNotifsNeedTtl()) {
+            if (mTtlHelper != null) {
+                mTtlHelper.destroy();
+            }
+        } else {
+            if (mNotificationTimeoutReceiver != null) {
+                getContext().unregisterReceiver(mNotificationTimeoutReceiver);
+            }
+        }
+        if (mRestoreReceiver != null) {
+            getContext().unregisterReceiver(mRestoreReceiver);
+        }
+        if (mLocaleChangeReceiver != null) {
+            getContext().unregisterReceiver(mLocaleChangeReceiver);
+        }
+        if (mSettingsObserver != null) {
+            mSettingsObserver.destroy();
+        }
+        if (mRoleObserver != null) {
+            mRoleObserver.destroy();
+        }
         if (mShortcutHelper != null) {
             mShortcutHelper.destroy();
         }
-        mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_PREFERENCES);
-        mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES);
-        mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES);
-        mStatsManager.clearPullAtomCallback(DND_MODE_RULE);
-        mAppOps.stopWatchingMode(mAppOpsListener);
-        mAlarmManager.cancelAll();
+        if (mStatsManager != null) {
+            mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_PREFERENCES);
+            mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES);
+            mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES);
+            mStatsManager.clearPullAtomCallback(DND_MODE_RULE);
+        }
+        if (mAppOps != null) {
+            mAppOps.stopWatchingMode(mAppOpsListener);
+        }
+        if (mAlarmManager != null) {
+            mAlarmManager.cancelAll();
+        }
     }
 
     protected String[] getStringArrayResource(int key) {
@@ -2966,6 +2992,26 @@
     }
 
     @Override
+    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
+        if (!Flags.useSsmUserSwitchSignal()) {
+            return;
+        }
+        final int userId = to.getUserIdentifier();
+        mUserProfiles.updateCache(getContext());
+        if (!mUserProfiles.isProfileUser(userId, getContext())) {
+            // reload per-user settings
+            mSettingsObserver.update(null);
+            // Refresh managed services
+            mConditionProviders.onUserSwitched(userId);
+            mListeners.onUserSwitched(userId);
+            mZenModeHelper.onUserSwitched(userId);
+            mPreferencesHelper.syncChannelsBypassingDnd();
+        }
+        // assistant is the only thing that cares about managed profiles specifically
+        mAssistants.onUserSwitched(userId);
+    }
+
+    @Override
     public void onUserStopping(@NonNull TargetUser user) {
         mHandler.post(() -> {
             Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "notifHistoryStopUser");
@@ -5786,7 +5832,16 @@
 
         @Override
         public ComponentName getEffectsSuppressor() {
-            return !mEffectsSuppressors.isEmpty() ? mEffectsSuppressors.get(0) : null;
+            ComponentName suppressor = !mEffectsSuppressors.isEmpty()
+                    ? mEffectsSuppressors.get(0)
+                    : null;
+            if (isCallerSystemOrSystemUiOrShell() || suppressor == null
+                    || mPackageManagerInternal.isSameApp(suppressor.getPackageName(),
+                    Binder.getCallingUid(), UserHandle.getUserId(Binder.getCallingUid()))) {
+                return suppressor;
+            }
+
+            return null;
         }
 
         @Override
@@ -7179,7 +7234,15 @@
                 callingUid, userId, true, false, "cancelNotificationWithTag", pkg);
 
         // ensure opPkg is delegate if does not match pkg
-        int uid = resolveNotificationUid(opPkg, pkg, callingUid, userId);
+
+        int uid = INVALID_UID;
+
+        try {
+            uid = resolveNotificationUid(opPkg, pkg, callingUid, userId);
+        } catch (NameNotFoundException e) {
+            // package either never existed so there's no posted notification or it's being
+            // uninstalled so we'll be cleaning it up soon. log and return immediately below.
+        }
 
         if (uid == INVALID_UID) {
             Slog.w(TAG, opPkg + ":" + callingUid + " trying to cancel notification "
@@ -7273,7 +7336,13 @@
 
         // Can throw a SecurityException if the calling uid doesn't have permission to post
         // as "pkg"
-        final int notificationUid = resolveNotificationUid(opPkg, pkg, callingUid, userId);
+        int notificationUid = INVALID_UID;
+
+        try {
+            notificationUid = resolveNotificationUid(opPkg, pkg, callingUid, userId);
+        } catch (NameNotFoundException e) {
+            // not great -  throw immediately below
+        }
 
         if (notificationUid == INVALID_UID) {
             throw new SecurityException("Caller " + opPkg + ":" + callingUid
@@ -7699,6 +7768,9 @@
             return true;
         }
         // Check if an app has been given system exemption
+        if (ai.uid == Process.SYSTEM_UID) {
+            return false;
+        }
         return mAppOps.checkOpNoThrow(
                 AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, ai.uid,
                 ai.packageName) == MODE_ALLOWED;
@@ -7827,7 +7899,8 @@
     }
 
     @VisibleForTesting
-    int resolveNotificationUid(String callingPkg, String targetPkg, int callingUid, int userId) {
+    int resolveNotificationUid(String callingPkg, String targetPkg, int callingUid, int userId)
+            throws NameNotFoundException {
         if (userId == USER_ALL) {
             userId = USER_SYSTEM;
         }
@@ -7838,12 +7911,8 @@
             return callingUid;
         }
 
-        int targetUid = INVALID_UID;
-        try {
-            targetUid = mPackageManagerClient.getPackageUidAsUser(targetPkg, userId);
-        } catch (NameNotFoundException e) {
-            /* ignore, handled by caller */
-        }
+        int targetUid = mPackageManagerClient.getPackageUidAsUser(targetPkg, userId);
+
         // posted from app A on behalf of app B
         if (isCallerAndroid(callingPkg, callingUid)
                 || mPreferencesHelper.isDelegateAllowed(
@@ -7993,6 +8062,13 @@
             return false;
         }
 
+        if (Flags.rejectOldNotifications() && n.hasAppProvidedWhen() && n.getWhen() > 0
+                && (System.currentTimeMillis() - n.getWhen()) > NOTIFICATION_MAX_AGE_AT_POST) {
+            Slog.d(TAG, "Ignored enqueue for old " + n.getWhen() + " notification " + r.getKey());
+            mUsageStats.registerTooOldBlocked(r);
+            return false;
+        }
+
         return true;
     }
 
@@ -11248,6 +11324,9 @@
 
             // Lifetime extended notifications don't need to alert on state change.
             record.setPostSilently(true);
+            // We also set FLAG_ONLY_ALERT_ONCE to avoid the notification from HUN-ing again.
+            record.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
+
             mHandler.post(new EnqueueNotificationRunnable(record.getUser().getIdentifier(),
                     record, isAppForeground,
                     mPostNotificationTrackerFactory.newTracker(null)));
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index e960f4b..c09077e 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -257,6 +257,14 @@
         }
     }
 
+    public synchronized void registerTooOldBlocked(NotificationRecord notification) {
+        AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification);
+        for (AggregatedStats stats : aggregatedStatsArray) {
+            stats.numTooOld++;
+        }
+        releaseAggregatedStatsLocked(aggregatedStatsArray);
+    }
+
     @GuardedBy("this")
     private AggregatedStats[] getAggregatedStatsLocked(NotificationRecord record) {
         return getAggregatedStatsLocked(record.getSbn().getPackageName());
@@ -405,6 +413,7 @@
         public int numUndecoratedRemoteViews;
         public long mLastAccessTime;
         public int numImagesRemoved;
+        public int numTooOld;
 
         public AggregatedStats(Context context, String key) {
             this.key = key;
@@ -535,6 +544,7 @@
             maybeCount("note_over_alert_rate", (numAlertViolations - previous.numAlertViolations));
             maybeCount("note_over_quota", (numQuotaViolations - previous.numQuotaViolations));
             maybeCount("note_images_removed", (numImagesRemoved - previous.numImagesRemoved));
+            maybeCount("not_too_old", (numTooOld - previous.numTooOld));
             noisyImportance.maybeCount(previous.noisyImportance);
             quietImportance.maybeCount(previous.quietImportance);
             finalImportance.maybeCount(previous.finalImportance);
@@ -570,6 +580,7 @@
             previous.numAlertViolations = numAlertViolations;
             previous.numQuotaViolations = numQuotaViolations;
             previous.numImagesRemoved = numImagesRemoved;
+            previous.numTooOld = numTooOld;
             noisyImportance.update(previous.noisyImportance);
             quietImportance.update(previous.quietImportance);
             finalImportance.update(previous.finalImportance);
@@ -679,6 +690,8 @@
             output.append("numQuotaViolations=").append(numQuotaViolations).append("\n");
             output.append(indentPlusTwo);
             output.append("numImagesRemoved=").append(numImagesRemoved).append("\n");
+            output.append(indentPlusTwo);
+            output.append("numTooOld=").append(numTooOld).append("\n");
             output.append(indentPlusTwo).append(noisyImportance.toString()).append("\n");
             output.append(indentPlusTwo).append(quietImportance.toString()).append("\n");
             output.append(indentPlusTwo).append(finalImportance.toString()).append("\n");
@@ -725,6 +738,7 @@
             maybePut(dump, "notificationEnqueueRate", getEnqueueRate());
             maybePut(dump, "numAlertViolations", numAlertViolations);
             maybePut(dump, "numImagesRemoved", numImagesRemoved);
+            maybePut(dump, "numTooOld", numTooOld);
             noisyImportance.maybePut(dump, previous.noisyImportance);
             quietImportance.maybePut(dump, previous.quietImportance);
             finalImportance.maybePut(dump, previous.finalImportance);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 143bc5c..b589f49 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -486,6 +486,7 @@
         }
     }
 
+    @GuardedBy("mConfigLock")
     private ZenRule maybeRestoreRemovedRule(ZenModeConfig config, ZenRule ruleToAdd,
             AutomaticZenRule azrToAdd, @ConfigChangeOrigin int origin) {
         if (!Flags.modesApi()) {
@@ -1112,6 +1113,7 @@
      * <p>The rule's {@link ZenRule#condition} is cleared (meaning that an active rule will be
      * deactivated) unless the update has origin == {@link ZenModeConfig#UPDATE_ORIGIN_USER}.
      */
+    @GuardedBy("mConfigLock")
     private boolean populateZenRule(String pkg, AutomaticZenRule azr, ZenRule rule,
                          @ConfigChangeOrigin int origin, boolean isNew) {
         if (Flags.modesApi()) {
@@ -1261,12 +1263,14 @@
      *
      * <p>Returns {@code true} if the policy of the rule was modified.
      */
+    @GuardedBy("mConfigLock")
     private boolean updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy,
             boolean updateBitmask, boolean isNew) {
         if (newPolicy == null) {
             if (isNew) {
                 // Newly created rule with no provided policy; fill in with the default.
-                zenRule.zenPolicy = mDefaultConfig.toZenPolicy();
+                zenRule.zenPolicy =
+                        Flags.modesUi() ? mDefaultConfig.toZenPolicy() : mConfig.toZenPolicy();
                 return true;
             }
             // Otherwise, a null policy means no policy changes, so we can stop here.
@@ -1275,8 +1279,9 @@
 
         // If oldPolicy is null, we compare against the default policy when determining which
         // fields in the bitmask should be marked as updated.
-        ZenPolicy oldPolicy =
-                zenRule.zenPolicy != null ? zenRule.zenPolicy : mDefaultConfig.toZenPolicy();
+        ZenPolicy oldPolicy = zenRule.zenPolicy != null
+                ? zenRule.zenPolicy
+                : (Flags.modesUi() ? mDefaultConfig.toZenPolicy() : mConfig.toZenPolicy());
 
         // If this is updating a rule rather than creating a new one, keep any fields from the
         // old policy if they are unspecified in the new policy. For newly created rules, oldPolicy
@@ -2033,7 +2038,8 @@
                     // rule's policy fields should be set upon creation, this is a fallback to
                     // catch any that may have fallen through the cracks.
                     Log.wtf(TAG, "active automatic rule found with no specified policy: " + rule);
-                    policy.apply(mDefaultConfig.toZenPolicy());
+                    policy.apply(
+                            Flags.modesUi() ? mDefaultConfig.toZenPolicy() : mConfig.toZenPolicy());
                 }
             } else {
                 // active rule with no specified policy inherits the global config settings
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index af3db6c..bf6b652 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -128,3 +128,17 @@
   description: "Adds an IPCDataCache for notification channel/group lookups"
   bug: "331677193"
 }
+
+flag {
+  name: "use_ssm_user_switch_signal"
+  namespace: "systemui"
+  description: "This flag controls which signal is used to handle a user switch system event"
+  bug: "337077643"
+}
+
+flag {
+  name: "reject_old_notifications"
+  namespace: "systemui"
+  description: "This flag does not allow notifications older than 2 weeks old to be posted"
+  bug: "339833083"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java b/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java
index 681dd0b..96ab2cc 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java
@@ -33,6 +33,7 @@
 import android.os.BadParcelableException;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
 import android.os.PersistableBundle;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
@@ -41,7 +42,10 @@
 import android.system.Os;
 import android.util.Log;
 
+import com.android.internal.infra.AndroidFuture;
+
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Util methods for ensuring the Bundle passed in various methods are read-only and restricted to
@@ -78,16 +82,16 @@
             if (canMarshall(obj) || obj instanceof CursorWindow) {
                 continue;
             }
-
-            if (obj instanceof ParcelFileDescriptor) {
+            if (obj instanceof Bundle) {
+              sanitizeInferenceParams((Bundle) obj);
+            } else if (obj instanceof ParcelFileDescriptor) {
                 validatePfdReadOnly((ParcelFileDescriptor) obj);
             } else if (obj instanceof SharedMemory) {
                 ((SharedMemory) obj).setProtect(PROT_READ);
             } else if (obj instanceof Bitmap) {
-                if (((Bitmap) obj).isMutable()) {
-                    throw new BadParcelableException(
-                            "Encountered a mutable Bitmap in the Bundle at key : " + key);
-                }
+                validateBitmap((Bitmap) obj);
+            } else if (obj instanceof Parcelable[]) {
+                validateParcelableArray((Parcelable[]) obj);
             } else {
                 throw new BadParcelableException(
                         "Unsupported Parcelable type encountered in the Bundle: "
@@ -125,20 +129,20 @@
                 continue;
             }
 
-            if (obj instanceof ParcelFileDescriptor) {
+            if (obj instanceof Bundle) {
+                sanitizeResponseParams((Bundle) obj);
+            } else if (obj instanceof ParcelFileDescriptor) {
                 validatePfdReadOnly((ParcelFileDescriptor) obj);
             } else if (obj instanceof Bitmap) {
-                if (((Bitmap) obj).isMutable()) {
-                    throw new BadParcelableException(
-                            "Encountered a mutable Bitmap in the Bundle at key : " + key);
-                }
+                validateBitmap((Bitmap) obj);
+            } else if (obj instanceof Parcelable[]) {
+                validateParcelableArray((Parcelable[]) obj);
             } else {
                 throw new BadParcelableException(
                         "Unsupported Parcelable type encountered in the Bundle: "
                                 + obj.getClass().getSimpleName());
             }
         }
-        Log.e(TAG, "validateResponseParams : Finished");
     }
 
     /**
@@ -183,7 +187,8 @@
 
     public static IStreamingResponseCallback wrapWithValidation(
             IStreamingResponseCallback streamingResponseCallback,
-            Executor resourceClosingExecutor) {
+            Executor resourceClosingExecutor,
+            AndroidFuture future) {
         return new IStreamingResponseCallback.Stub() {
             @Override
             public void onNewContent(Bundle processedResult) throws RemoteException {
@@ -203,6 +208,7 @@
                     streamingResponseCallback.onSuccess(resultBundle);
                 } finally {
                     resourceClosingExecutor.execute(() -> tryCloseResource(resultBundle));
+                    future.complete(null);
                 }
             }
 
@@ -210,6 +216,7 @@
             public void onFailure(int errorCode, String errorMessage,
                     PersistableBundle errorParams) throws RemoteException {
                 streamingResponseCallback.onFailure(errorCode, errorMessage, errorParams);
+                future.completeExceptionally(new TimeoutException());
             }
 
             @Override
@@ -237,7 +244,8 @@
     }
 
     public static IResponseCallback wrapWithValidation(IResponseCallback responseCallback,
-            Executor resourceClosingExecutor) {
+            Executor resourceClosingExecutor,
+            AndroidFuture future) {
         return new IResponseCallback.Stub() {
             @Override
             public void onSuccess(Bundle resultBundle)
@@ -247,6 +255,7 @@
                     responseCallback.onSuccess(resultBundle);
                 } finally {
                     resourceClosingExecutor.execute(() -> tryCloseResource(resultBundle));
+                    future.complete(null);
                 }
             }
 
@@ -254,6 +263,7 @@
             public void onFailure(int errorCode, String errorMessage,
                     PersistableBundle errorParams) throws RemoteException {
                 responseCallback.onFailure(errorCode, errorMessage, errorParams);
+                future.completeExceptionally(new TimeoutException());
             }
 
             @Override
@@ -280,17 +290,20 @@
     }
 
 
-    public static ITokenInfoCallback wrapWithValidation(ITokenInfoCallback responseCallback) {
+    public static ITokenInfoCallback wrapWithValidation(ITokenInfoCallback responseCallback,
+            AndroidFuture future) {
         return new ITokenInfoCallback.Stub() {
             @Override
             public void onSuccess(TokenInfo tokenInfo) throws RemoteException {
                 responseCallback.onSuccess(tokenInfo);
+                future.complete(null);
             }
 
             @Override
             public void onFailure(int errorCode, String errorMessage, PersistableBundle errorParams)
                     throws RemoteException {
                 responseCallback.onFailure(errorCode, errorMessage, errorParams);
+                future.completeExceptionally(new TimeoutException());
             }
         };
     }
@@ -310,6 +323,26 @@
         }
     }
 
+    private static void validateParcelableArray(Parcelable[] parcelables) {
+        if (parcelables.length > 0
+                && parcelables[0] instanceof ParcelFileDescriptor) {
+            // Safe to cast
+            validatePfdsReadOnly(parcelables);
+        } else if (parcelables.length > 0
+                && parcelables[0] instanceof Bitmap) {
+            validateBitmapsImmutable(parcelables);
+        } else {
+            throw new BadParcelableException(
+                    "Could not cast to any known parcelable array");
+        }
+    }
+
+    public static void validatePfdsReadOnly(Parcelable[] pfds) {
+        for (Parcelable pfd : pfds) {
+            validatePfdReadOnly((ParcelFileDescriptor) pfd);
+        }
+    }
+
     public static void validatePfdReadOnly(ParcelFileDescriptor pfd) {
         if (pfd == null) {
             return;
@@ -326,6 +359,19 @@
         }
     }
 
+    private static void validateBitmap(Bitmap obj) {
+        if (obj.isMutable()) {
+            throw new BadParcelableException(
+                    "Encountered a mutable Bitmap in the Bundle at key : " + obj);
+        }
+    }
+
+    private static void validateBitmapsImmutable(Parcelable[] bitmaps) {
+        for (Parcelable bitmap : bitmaps) {
+            validateBitmap((Bitmap) bitmap);
+        }
+    }
+
     public static void tryCloseResource(Bundle bundle) {
         if (bundle == null || bundle.isEmpty() || !bundle.hasFileDescriptors()) {
             return;
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index 99401a1..cdc1a5e 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -16,6 +16,11 @@
 
 package com.android.server.ondeviceintelligence;
 
+import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.DEVICE_CONFIG_UPDATE_BUNDLE_KEY;
+import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_LOADED_BUNDLE_KEY;
+import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_UNLOADED_BUNDLE_KEY;
+import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY;
+
 import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeInferenceParams;
 import static com.android.server.ondeviceintelligence.BundleUtil.validatePfdReadOnly;
 import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeStateParams;
@@ -29,6 +34,7 @@
 import android.app.AppGlobals;
 import android.app.ondeviceintelligence.DownloadCallback;
 import android.app.ondeviceintelligence.Feature;
+import android.app.ondeviceintelligence.FeatureDetails;
 import android.app.ondeviceintelligence.IDownloadCallback;
 import android.app.ondeviceintelligence.IFeatureCallback;
 import android.app.ondeviceintelligence.IFeatureDetailsCallback;
@@ -41,6 +47,7 @@
 import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
@@ -59,6 +66,7 @@
 import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
 import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
 import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
@@ -77,13 +85,17 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.ondeviceintelligence.callbacks.ListenableDownloadCallback;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This is the system service for handling calls on the
@@ -104,13 +116,23 @@
 
     /** Handler message to {@link #resetTemporaryServices()} */
     private static final int MSG_RESET_TEMPORARY_SERVICE = 0;
+    /** Handler message to clean up temporary broadcast keys. */
+    private static final int MSG_RESET_BROADCAST_KEYS = 1;
+    /** Handler message to clean up temporary config namespace. */
+    private static final int MSG_RESET_CONFIG_NAMESPACE = 2;
 
     /** Default value in absence of {@link DeviceConfig} override. */
     private static final boolean DEFAULT_SERVICE_ENABLED = true;
     private static final String NAMESPACE_ON_DEVICE_INTELLIGENCE = "ondeviceintelligence";
 
+    private static final String SYSTEM_PACKAGE = "android";
+
+
     private final Executor resourceClosingExecutor = Executors.newCachedThreadPool();
     private final Executor callbackExecutor = Executors.newCachedThreadPool();
+    private final Executor broadcastExecutor = Executors.newCachedThreadPool();
+    private final Executor mConfigExecutor = Executors.newCachedThreadPool();
+
 
     private final Context mContext;
     protected final Object mLock = new Object();
@@ -122,12 +144,23 @@
 
     @GuardedBy("mLock")
     private String[] mTemporaryServiceNames;
+    @GuardedBy("mLock")
+    private String[] mTemporaryBroadcastKeys;
+    @GuardedBy("mLock")
+    private String mBroadcastPackageName;
+    @GuardedBy("mLock")
+    private String mTemporaryConfigNamespace;
+
+    private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
+            this::sendUpdatedConfig;
+
 
     /**
      * Handler used to reset the temporary service names.
      */
-    @GuardedBy("mLock")
     private Handler mTemporaryHandler;
+    private final @NonNull Handler mMainHandler = new Handler(Looper.getMainLooper());
+
 
     public OnDeviceIntelligenceManagerService(Context context) {
         super(context);
@@ -187,8 +220,16 @@
                     return;
                 }
                 ensureRemoteIntelligenceServiceInitialized();
-                mRemoteOnDeviceIntelligenceService.run(
-                        service -> service.getVersion(remoteCallback));
+                mRemoteOnDeviceIntelligenceService.postAsync(
+                        service -> {
+                            AndroidFuture future = new AndroidFuture();
+                            service.getVersion(new RemoteCallback(
+                                    result -> {
+                                        remoteCallback.sendResult(result);
+                                        future.complete(null);
+                                    }));
+                            return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
+                        });
             }
 
             @Override
@@ -208,8 +249,25 @@
                 }
                 ensureRemoteIntelligenceServiceInitialized();
                 int callerUid = Binder.getCallingUid();
-                mRemoteOnDeviceIntelligenceService.run(
-                        service -> service.getFeature(callerUid, id, featureCallback));
+                mRemoteOnDeviceIntelligenceService.postAsync(
+                        service -> {
+                            AndroidFuture future = new AndroidFuture();
+                            service.getFeature(callerUid, id, new IFeatureCallback.Stub() {
+                                @Override
+                                public void onSuccess(Feature result) throws RemoteException {
+                                    featureCallback.onSuccess(result);
+                                    future.complete(null);
+                                }
+
+                                @Override
+                                public void onFailure(int errorCode, String errorMessage,
+                                        PersistableBundle errorParams) throws RemoteException {
+                                    featureCallback.onFailure(errorCode, errorMessage, errorParams);
+                                    future.completeExceptionally(new TimeoutException());
+                                }
+                            });
+                            return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
+                        });
             }
 
             @Override
@@ -229,9 +287,29 @@
                 }
                 ensureRemoteIntelligenceServiceInitialized();
                 int callerUid = Binder.getCallingUid();
-                mRemoteOnDeviceIntelligenceService.run(
-                        service -> service.listFeatures(callerUid,
-                                listFeaturesCallback));
+                mRemoteOnDeviceIntelligenceService.postAsync(
+                        service -> {
+                            AndroidFuture future = new AndroidFuture();
+                            service.listFeatures(callerUid,
+                                    new IListFeaturesCallback.Stub() {
+                                        @Override
+                                        public void onSuccess(List<Feature> result)
+                                                throws RemoteException {
+                                            listFeaturesCallback.onSuccess(result);
+                                            future.complete(null);
+                                        }
+
+                                        @Override
+                                        public void onFailure(int errorCode, String errorMessage,
+                                                PersistableBundle errorParams)
+                                                throws RemoteException {
+                                            listFeaturesCallback.onFailure(errorCode, errorMessage,
+                                                    errorParams);
+                                            future.completeExceptionally(new TimeoutException());
+                                        }
+                                    });
+                            return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
+                        });
             }
 
             @Override
@@ -253,9 +331,29 @@
                 }
                 ensureRemoteIntelligenceServiceInitialized();
                 int callerUid = Binder.getCallingUid();
-                mRemoteOnDeviceIntelligenceService.run(
-                        service -> service.getFeatureDetails(callerUid, feature,
-                                featureDetailsCallback));
+                mRemoteOnDeviceIntelligenceService.postAsync(
+                        service -> {
+                            AndroidFuture future = new AndroidFuture();
+                            service.getFeatureDetails(callerUid, feature,
+                                    new IFeatureDetailsCallback.Stub() {
+                                        @Override
+                                        public void onSuccess(FeatureDetails result)
+                                                throws RemoteException {
+                                            future.complete(null);
+                                            featureDetailsCallback.onSuccess(result);
+                                        }
+
+                                        @Override
+                                        public void onFailure(int errorCode, String errorMessage,
+                                                PersistableBundle errorParams)
+                                                throws RemoteException {
+                                            future.completeExceptionally(null);
+                                            featureDetailsCallback.onFailure(errorCode,
+                                                    errorMessage, errorParams);
+                                        }
+                                    });
+                            return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
+                        });
             }
 
             @Override
@@ -276,10 +374,20 @@
                 }
                 ensureRemoteIntelligenceServiceInitialized();
                 int callerUid = Binder.getCallingUid();
-                mRemoteOnDeviceIntelligenceService.run(
-                        service -> service.requestFeatureDownload(callerUid, feature,
-                                wrapCancellationFuture(cancellationSignalFuture),
-                                downloadCallback));
+                mRemoteOnDeviceIntelligenceService.postAsync(
+                        service -> {
+                            AndroidFuture future = new AndroidFuture();
+                            ListenableDownloadCallback listenableDownloadCallback =
+                                    new ListenableDownloadCallback(
+                                            downloadCallback,
+                                            mMainHandler, future, getIdleTimeoutMs());
+                            service.requestFeatureDownload(callerUid, feature,
+                                    wrapCancellationFuture(cancellationSignalFuture),
+                                    listenableDownloadCallback);
+                            return future; // this future has no timeout because, actual download
+                            // might take long, but we fail early if there is no progress callbacks.
+                        }
+                );
             }
 
 
@@ -306,11 +414,15 @@
                     }
                     ensureRemoteInferenceServiceInitialized();
                     int callerUid = Binder.getCallingUid();
-                    result = mRemoteInferenceService.post(
-                            service -> service.requestTokenInfo(callerUid, feature,
-                                    request,
-                                    wrapCancellationFuture(cancellationSignalFuture),
-                                    wrapWithValidation(tokenInfoCallback)));
+                    result = mRemoteInferenceService.postAsync(
+                            service -> {
+                                AndroidFuture future = new AndroidFuture();
+                                service.requestTokenInfo(callerUid, feature,
+                                        request,
+                                        wrapCancellationFuture(cancellationSignalFuture),
+                                        wrapWithValidation(tokenInfoCallback, future));
+                                return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
+                            });
                     result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request),
                             resourceClosingExecutor);
                 } finally {
@@ -345,13 +457,18 @@
                     }
                     ensureRemoteInferenceServiceInitialized();
                     int callerUid = Binder.getCallingUid();
-                    result = mRemoteInferenceService.post(
-                            service -> service.processRequest(callerUid, feature,
-                                    request,
-                                    requestType,
-                                    wrapCancellationFuture(cancellationSignalFuture),
-                                    wrapProcessingFuture(processingSignalFuture),
-                                    wrapWithValidation(responseCallback, resourceClosingExecutor)));
+                    result = mRemoteInferenceService.postAsync(
+                            service -> {
+                                AndroidFuture future = new AndroidFuture();
+                                service.processRequest(callerUid, feature,
+                                        request,
+                                        requestType,
+                                        wrapCancellationFuture(cancellationSignalFuture),
+                                        wrapProcessingFuture(processingSignalFuture),
+                                        wrapWithValidation(responseCallback,
+                                                resourceClosingExecutor, future));
+                                return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
+                            });
                     result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request),
                             resourceClosingExecutor);
                 } finally {
@@ -385,13 +502,18 @@
                     }
                     ensureRemoteInferenceServiceInitialized();
                     int callerUid = Binder.getCallingUid();
-                    result = mRemoteInferenceService.post(
-                            service -> service.processRequestStreaming(callerUid,
-                                    feature,
-                                    request, requestType,
-                                    wrapCancellationFuture(cancellationSignalFuture),
-                                    wrapProcessingFuture(processingSignalFuture),
-                                    streamingCallback));
+                    result = mRemoteInferenceService.postAsync(
+                            service -> {
+                                AndroidFuture future = new AndroidFuture();
+                                service.processRequestStreaming(callerUid,
+                                        feature,
+                                        request, requestType,
+                                        wrapCancellationFuture(cancellationSignalFuture),
+                                        wrapProcessingFuture(processingSignalFuture),
+                                        wrapWithValidation(streamingCallback,
+                                                resourceClosingExecutor, future));
+                                return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
+                            });
                     result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request),
                             resourceClosingExecutor);
                 } finally {
@@ -480,10 +602,14 @@
                                     @NonNull IOnDeviceSandboxedInferenceService service) {
                                 try {
                                     ensureRemoteIntelligenceServiceInitialized();
-                                    mRemoteOnDeviceIntelligenceService.run(
-                                            IOnDeviceIntelligenceService::notifyInferenceServiceConnected);
                                     service.registerRemoteStorageService(
                                             getIRemoteStorageService());
+                                    mRemoteOnDeviceIntelligenceService.run(
+                                            IOnDeviceIntelligenceService::notifyInferenceServiceConnected);
+                                    broadcastExecutor.execute(
+                                            () -> registerModelLoadingBroadcasts(service));
+                                    mConfigExecutor.execute(
+                                            () -> registerDeviceConfigChangeListener());
                                 } catch (RemoteException ex) {
                                     Slog.w(TAG, "Failed to send connected event", ex);
                                 }
@@ -493,6 +619,108 @@
         }
     }
 
+    private void registerModelLoadingBroadcasts(IOnDeviceSandboxedInferenceService service) {
+        String[] modelBroadcastKeys;
+        try {
+            modelBroadcastKeys = getBroadcastKeys();
+        } catch (Resources.NotFoundException e) {
+            Slog.d(TAG, "Skipping model broadcasts as broadcast intents configured.");
+            return;
+        }
+
+        Bundle bundle = new Bundle();
+        bundle.putBoolean(REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY, true);
+        try {
+            service.updateProcessingState(bundle, new IProcessingUpdateStatusCallback.Stub() {
+                @Override
+                public void onSuccess(PersistableBundle statusParams) {
+                    Binder.clearCallingIdentity();
+                    synchronized (mLock) {
+                        if (statusParams.containsKey(MODEL_LOADED_BUNDLE_KEY)) {
+                            String modelLoadedBroadcastKey = modelBroadcastKeys[0];
+                            if (modelLoadedBroadcastKey != null
+                                    && !modelLoadedBroadcastKey.isEmpty()) {
+                                final Intent intent = new Intent(modelLoadedBroadcastKey);
+                                intent.setPackage(mBroadcastPackageName);
+                                mContext.sendBroadcast(intent,
+                                        Manifest.permission.USE_ON_DEVICE_INTELLIGENCE);
+                            }
+                        } else if (statusParams.containsKey(MODEL_UNLOADED_BUNDLE_KEY)) {
+                            String modelUnloadedBroadcastKey = modelBroadcastKeys[1];
+                            if (modelUnloadedBroadcastKey != null
+                                    && !modelUnloadedBroadcastKey.isEmpty()) {
+                                final Intent intent = new Intent(modelUnloadedBroadcastKey);
+                                intent.setPackage(mBroadcastPackageName);
+                                mContext.sendBroadcast(intent,
+                                        Manifest.permission.USE_ON_DEVICE_INTELLIGENCE);
+                            }
+                        }
+                    }
+                }
+
+                @Override
+                public void onFailure(int errorCode, String errorMessage) {
+                    Slog.e(TAG, "Failed to register model loading callback with status code",
+                            new OnDeviceIntelligenceException(errorCode, errorMessage));
+                }
+            });
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to register model loading callback with status code", e);
+        }
+    }
+
+    private void registerDeviceConfigChangeListener() {
+        Log.e(TAG, "registerDeviceConfigChangeListener");
+        String configNamespace = getConfigNamespace();
+        if (configNamespace.isEmpty()) {
+            Slog.e(TAG, "config_defaultOnDeviceIntelligenceDeviceConfigNamespace is empty");
+            return;
+        }
+        DeviceConfig.addOnPropertiesChangedListener(
+                configNamespace,
+                mConfigExecutor,
+                mOnPropertiesChangedListener);
+    }
+
+    private String getConfigNamespace() {
+        synchronized (mLock) {
+            if (mTemporaryConfigNamespace != null) {
+                return mTemporaryConfigNamespace;
+            }
+
+            return mContext.getResources().getString(
+                    R.string.config_defaultOnDeviceIntelligenceDeviceConfigNamespace);
+        }
+    }
+
+    private void sendUpdatedConfig(
+            DeviceConfig.Properties props) {
+        Log.e(TAG, "sendUpdatedConfig");
+
+        PersistableBundle persistableBundle = new PersistableBundle();
+        for (String key : props.getKeyset()) {
+            persistableBundle.putString(key, props.getString(key, ""));
+        }
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(DEVICE_CONFIG_UPDATE_BUNDLE_KEY, persistableBundle);
+        ensureRemoteInferenceServiceInitialized();
+        Log.e(TAG, "sendUpdatedConfig: BUNDLE: " + bundle);
+
+        mRemoteInferenceService.run(service -> service.updateProcessingState(bundle,
+                new IProcessingUpdateStatusCallback.Stub() {
+                    @Override
+                    public void onSuccess(PersistableBundle result) {
+                        Slog.d(TAG, "Config update successful." + result);
+                    }
+
+                    @Override
+                    public void onFailure(int errorCode, String errorMessage) {
+                        Slog.e(TAG, "Config update failed with code ["
+                                + String.valueOf(errorCode) + "] and message = " + errorMessage);
+                    }
+                }));
+    }
+
     @NonNull
     private IRemoteStorageService.Stub getIRemoteStorageService() {
         return new IRemoteStorageService.Stub() {
@@ -629,6 +857,20 @@
                         R.string.config_defaultOnDeviceSandboxedInferenceService)};
     }
 
+    protected String[] getBroadcastKeys() throws Resources.NotFoundException {
+        // TODO 329240495 : Consider a small class with explicit field names for the two services
+        synchronized (mLock) {
+            if (mTemporaryBroadcastKeys != null && mTemporaryBroadcastKeys.length == 2) {
+                return mTemporaryBroadcastKeys;
+            }
+        }
+
+        return new String[]{mContext.getResources().getString(
+                R.string.config_onDeviceIntelligenceModelLoadedBroadcastKey),
+                mContext.getResources().getString(
+                        R.string.config_onDeviceIntelligenceModelUnloadedBroadcastKey)};
+    }
+
     @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
     public void setTemporaryServices(@NonNull String[] componentNames, int durationMs) {
         Objects.requireNonNull(componentNames);
@@ -645,32 +887,48 @@
                 mRemoteOnDeviceIntelligenceService.unbind();
                 mRemoteOnDeviceIntelligenceService = null;
             }
-            if (mTemporaryHandler == null) {
-                mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
-                    @Override
-                    public void handleMessage(Message msg) {
-                        if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
-                            synchronized (mLock) {
-                                resetTemporaryServices();
-                            }
-                        } else {
-                            Slog.wtf(TAG, "invalid handler msg: " + msg);
-                        }
-                    }
-                };
-            } else {
-                mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
-            }
 
             if (durationMs != -1) {
-                mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
+                getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE,
+                        durationMs);
+            }
+        }
+    }
+
+    @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+    public void setModelBroadcastKeys(@NonNull String[] broadcastKeys, String receiverPackageName,
+            int durationMs) {
+        Objects.requireNonNull(broadcastKeys);
+        enforceShellOnly(Binder.getCallingUid(), "setModelBroadcastKeys");
+        mContext.enforceCallingPermission(
+                Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+        synchronized (mLock) {
+            mTemporaryBroadcastKeys = broadcastKeys;
+            mBroadcastPackageName = receiverPackageName;
+            if (durationMs != -1) {
+                getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_BROADCAST_KEYS, durationMs);
+            }
+        }
+    }
+
+    @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+    public void setTemporaryDeviceConfigNamespace(@NonNull String configNamespace,
+            int durationMs) {
+        Objects.requireNonNull(configNamespace);
+        enforceShellOnly(Binder.getCallingUid(), "setTemporaryDeviceConfigNamespace");
+        mContext.enforceCallingPermission(
+                Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+        synchronized (mLock) {
+            mTemporaryConfigNamespace = configNamespace;
+            if (durationMs != -1) {
+                getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_CONFIG_NAMESPACE,
+                        durationMs);
             }
         }
     }
 
     @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
     public void resetTemporaryServices() {
-        enforceShellOnly(Binder.getCallingUid(), "resetTemporaryServices");
         mContext.enforceCallingPermission(
                 Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
         synchronized (mLock) {
@@ -751,4 +1009,34 @@
             }
         }
     }
+
+    private synchronized Handler getTemporaryHandler() {
+        if (mTemporaryHandler == null) {
+            mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
+                @Override
+                public void handleMessage(Message msg) {
+                    synchronized (mLock) {
+                        if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
+                            resetTemporaryServices();
+                        } else if (msg.what == MSG_RESET_BROADCAST_KEYS) {
+                            mTemporaryBroadcastKeys = null;
+                            mBroadcastPackageName = SYSTEM_PACKAGE;
+                        } else if (msg.what == MSG_RESET_CONFIG_NAMESPACE) {
+                            mTemporaryConfigNamespace = null;
+                        } else {
+                            Slog.wtf(TAG, "invalid handler msg: " + msg);
+                        }
+                    }
+                }
+            };
+        }
+
+        return mTemporaryHandler;
+    }
+
+    private long getIdleTimeoutMs() {
+        return Settings.Secure.getLongForUser(mContext.getContentResolver(),
+                Settings.Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, TimeUnit.HOURS.toMillis(1),
+                mContext.getUserId());
+    }
 }
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
index a76d8a3..d2c84fa 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
@@ -17,6 +17,7 @@
 package com.android.server.ondeviceintelligence;
 
 import android.annotation.NonNull;
+import android.os.Binder;
 import android.os.ShellCommand;
 
 import java.io.PrintWriter;
@@ -43,6 +44,10 @@
                 return setTemporaryServices();
             case "get-services":
                 return getConfiguredServices();
+            case "set-model-broadcasts":
+                return setBroadcastKeys();
+            case "set-deviceconfig-namespace":
+                return setDeviceConfigNamespace();
             default:
                 return handleDefaultCommands(cmd);
         }
@@ -62,14 +67,26 @@
         pw.println("    To reset, call without any arguments.");
 
         pw.println("  get-services To get the names of services that are currently being used.");
+        pw.println(
+                "  set-model-broadcasts [ModelLoadedBroadcastKey] [ModelUnloadedBroadcastKey] "
+                        + "[ReceiverPackageName] "
+                        + "[DURATION] To set the names of broadcast intent keys that are to be "
+                        + "emitted for cts tests.");
+        pw.println(
+                "  set-deviceconfig-namespace [DeviceConfigNamespace] "
+                        + "[DURATION] To set the device config namespace "
+                        + "to use for cts tests.");
     }
 
     private int setTemporaryServices() {
         final PrintWriter out = getOutPrintWriter();
         final String intelligenceServiceName = getNextArg();
         final String inferenceServiceName = getNextArg();
+
         if (getRemainingArgsCount() == 0 && intelligenceServiceName == null
                 && inferenceServiceName == null) {
+            OnDeviceIntelligenceManagerService.enforceShellOnly(Binder.getCallingUid(),
+                    "resetTemporaryServices");
             mService.resetTemporaryServices();
             out.println("OnDeviceIntelligenceManagerService temporary reset. ");
             return 0;
@@ -79,7 +96,8 @@
         Objects.requireNonNull(inferenceServiceName);
         final int duration = Integer.parseInt(getNextArgRequired());
         mService.setTemporaryServices(
-                new String[]{intelligenceServiceName, inferenceServiceName}, duration);
+                new String[]{intelligenceServiceName, inferenceServiceName},
+                duration);
         out.println("OnDeviceIntelligenceService temporarily set to " + intelligenceServiceName
                 + " \n and \n OnDeviceTrustedInferenceService set to " + inferenceServiceName
                 + " for " + duration + "ms");
@@ -93,4 +111,34 @@
                 + " \n and \n OnDeviceTrustedInferenceService set to : " + services[1]);
         return 0;
     }
+
+    private int setBroadcastKeys() {
+        final PrintWriter out = getOutPrintWriter();
+        final String modelLoadedKey = getNextArgRequired();
+        final String modelUnloadedKey = getNextArgRequired();
+        final String receiverPackageName = getNextArg();
+
+        final int duration = Integer.parseInt(getNextArgRequired());
+        mService.setModelBroadcastKeys(
+                new String[]{modelLoadedKey, modelUnloadedKey}, receiverPackageName, duration);
+        out.println("OnDeviceIntelligence Model Loading broadcast keys temporarily set to "
+                + modelLoadedKey
+                + " \n and \n OnDeviceTrustedInferenceService set to " + modelUnloadedKey
+                + "\n and Package name set to : " + receiverPackageName
+                + " for " + duration + "ms");
+        return 0;
+    }
+
+    private int setDeviceConfigNamespace() {
+        final PrintWriter out = getOutPrintWriter();
+        final String configNamespace = getNextArg();
+
+        final int duration = Integer.parseInt(getNextArgRequired());
+        mService.setTemporaryDeviceConfigNamespace(configNamespace, duration);
+        out.println("OnDeviceIntelligence DeviceConfig Namespace temporarily set to "
+                + configNamespace
+                + " for " + duration + "ms");
+        return 0;
+    }
+
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
index 48258d7..ac9747a 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
@@ -22,17 +22,21 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.provider.Settings;
 import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
 import android.service.ondeviceintelligence.OnDeviceIntelligenceService;
 
 import com.android.internal.infra.ServiceConnector;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * Manages the connection to the remote on-device intelligence service. Also, handles unbinding
  * logic set by the service implementation via a Secure Settings flag.
  */
 public class RemoteOnDeviceIntelligenceService extends
         ServiceConnector.Impl<IOnDeviceIntelligenceService> {
+    private static final long LONG_TIMEOUT = TimeUnit.HOURS.toMillis(4);
     private static final String TAG =
             RemoteOnDeviceIntelligenceService.class.getSimpleName();
 
@@ -48,9 +52,15 @@
     }
 
     @Override
+    protected long getRequestTimeoutMs() {
+        return LONG_TIMEOUT;
+    }
+
+    @Override
     protected long getAutoDisconnectTimeoutMs() {
-        // Disable automatic unbinding.
-        // TODO: add logic to fetch this flag via SecureSettings.
-        return -1;
+        return Settings.Secure.getLongForUser(mContext.getContentResolver(),
+                Settings.Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS,
+                TimeUnit.SECONDS.toMillis(30),
+                mContext.getUserId());
     }
 }
diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
index 69ba1d2..18b1383 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
@@ -22,18 +22,24 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.provider.Settings;
 import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
 import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
 
 import com.android.internal.infra.ServiceConnector;
 
+import java.util.concurrent.TimeUnit;
+
 
 /**
- * Manages the connection to the remote on-device sand boxed inference service. Also, handles unbinding
+ * Manages the connection to the remote on-device sand boxed inference service. Also, handles
+ * unbinding
  * logic set by the service implementation via a SecureSettings flag.
  */
 public class RemoteOnDeviceSandboxedInferenceService extends
         ServiceConnector.Impl<IOnDeviceSandboxedInferenceService> {
+    private static final long LONG_TIMEOUT = TimeUnit.HOURS.toMillis(1);
+
     /**
      * Creates an instance of {@link ServiceConnector}
      *
@@ -54,11 +60,17 @@
         connect();
     }
 
+    @Override
+    protected long getRequestTimeoutMs() {
+        return LONG_TIMEOUT;
+    }
+
 
     @Override
     protected long getAutoDisconnectTimeoutMs() {
-        // Disable automatic unbinding.
-        // TODO: add logic to fetch this flag via SecureSettings.
-        return -1;
+        return Settings.Secure.getLongForUser(mContext.getContentResolver(),
+                Settings.Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS,
+                TimeUnit.SECONDS.toMillis(30),
+                mContext.getUserId());
     }
 }
diff --git a/services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java b/services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java
new file mode 100644
index 0000000..32f0698
--- /dev/null
+++ b/services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.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 com.android.server.ondeviceintelligence.callbacks;
+
+import android.app.ondeviceintelligence.IDownloadCallback;
+import android.os.Handler;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * This class extends the {@link IDownloadCallback} and adds a timeout Runnable to the callback
+ * such that, in the case where the callback methods are not invoked, we do not have to wait for
+ * timeout based on {@link #onDownloadCompleted} which might take minutes or hours to complete in
+ * some cases. Instead, in such cases we rely on the remote service sending progress updates and if
+ * there are *no* progress callbacks in the duration of {@link #idleTimeoutMs}, we can assume the
+ * download will not complete and enabling faster cleanup.
+ */
+public class ListenableDownloadCallback extends IDownloadCallback.Stub implements Runnable {
+    private final IDownloadCallback callback;
+    private final Handler handler;
+    private final AndroidFuture future;
+    private final long idleTimeoutMs;
+
+    /**
+     * Constructor to create a ListenableDownloadCallback.
+     *
+     * @param callback      callback to send download updates to caller.
+     * @param handler       handler to schedule timeout runnable.
+     * @param future        future to complete to signal the callback has reached a terminal state.
+     * @param idleTimeoutMs timeout within which download updates should be received.
+     */
+    public ListenableDownloadCallback(IDownloadCallback callback, Handler handler,
+            AndroidFuture future,
+            long idleTimeoutMs) {
+        this.callback = callback;
+        this.handler = handler;
+        this.future = future;
+        this.idleTimeoutMs = idleTimeoutMs;
+        handler.postDelayed(this,
+                idleTimeoutMs); // init the timeout runnable in case no callback is ever invoked
+    }
+
+    @Override
+    public void onDownloadStarted(long bytesToDownload) throws RemoteException {
+        callback.onDownloadStarted(bytesToDownload);
+        handler.removeCallbacks(this);
+        handler.postDelayed(this, idleTimeoutMs);
+    }
+
+    @Override
+    public void onDownloadProgress(long bytesDownloaded) throws RemoteException {
+        callback.onDownloadProgress(bytesDownloaded);
+        handler.removeCallbacks(this); // remove previously queued timeout tasks.
+        handler.postDelayed(this, idleTimeoutMs); // queue fresh timeout task for next update.
+    }
+
+    @Override
+    public void onDownloadFailed(int failureStatus,
+            String errorMessage, PersistableBundle errorParams) throws RemoteException {
+        callback.onDownloadFailed(failureStatus, errorMessage, errorParams);
+        handler.removeCallbacks(this);
+        future.completeExceptionally(new TimeoutException());
+    }
+
+    @Override
+    public void onDownloadCompleted(
+            android.os.PersistableBundle downloadParams) throws RemoteException {
+        callback.onDownloadCompleted(downloadParams);
+        handler.removeCallbacks(this);
+        future.complete(null);
+    }
+
+    @Override
+    public void run() {
+        future.completeExceptionally(
+                new TimeoutException()); // complete the future as we haven't received updates
+        // for download progress.
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 8a85328..71a7d0d 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -46,6 +46,7 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.LocalLog;
+import android.util.MutableBoolean;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.Xml;
@@ -104,6 +105,7 @@
     private final TelephonyManager mTelephonyManager;
     private final ArraySet<String> mBugreportAllowlistedPackages;
     private final BugreportFileManager mBugreportFileManager;
+    private static final FeatureFlags sFeatureFlags = new FeatureFlagsImpl();
 
 
     @GuardedBy("mLock")
@@ -429,9 +431,51 @@
         ensureUserCanTakeBugReport(bugreportMode);
 
         Slogf.i(TAG, "Starting bugreport for %s / %d", callingPackage, callingUid);
-        synchronized (mLock) {
-            startBugreportLocked(callingUid, callingPackage, bugreportFd, screenshotFd,
-                    bugreportMode, bugreportFlags, listener, isScreenshotRequested);
+        final MutableBoolean handoffLock = new MutableBoolean(false);
+        if (sFeatureFlags.asyncStartBugreport()) {
+            synchronized (handoffLock) {
+                new Thread(()-> {
+                    try {
+                        synchronized (mLock) {
+                            synchronized (handoffLock) {
+                                handoffLock.value = true;
+                                handoffLock.notifyAll();
+                            }
+                            startBugreportLocked(
+                                    callingUid,
+                                    callingPackage,
+                                    bugreportFd,
+                                    screenshotFd,
+                                    bugreportMode,
+                                    bugreportFlags,
+                                    listener,
+                                    isScreenshotRequested);
+                        }
+                    } catch (Exception e) {
+                        Slog.e(TAG, "Cannot start a new bugreport due to an unknown error", e);
+                        reportError(listener, IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR);
+                    }
+                }, "BugreportManagerServiceThread").start();
+                try {
+                    while (!handoffLock.value) { // handle the rare case of a spurious wakeup
+                        handoffLock.wait(DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS);
+                    }
+                } catch (InterruptedException e) {
+                    Slog.e(TAG, "Unexpectedly interrupted waiting for startBugreportLocked", e);
+                }
+            }
+        } else {
+            synchronized (mLock) {
+                startBugreportLocked(
+                        callingUid,
+                        callingPackage,
+                        bugreportFd,
+                        screenshotFd,
+                        bugreportMode,
+                        bugreportFlags,
+                        listener,
+                        isScreenshotRequested);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/os/core_os_flags.aconfig b/services/core/java/com/android/server/os/core_os_flags.aconfig
index ae33df8..efdc9b8 100644
--- a/services/core/java/com/android/server/os/core_os_flags.aconfig
+++ b/services/core/java/com/android/server/os/core_os_flags.aconfig
@@ -7,3 +7,13 @@
     description: "Use proto tombstones as source of truth for adding to dropbox"
     bug: "323857385"
 }
+
+flag {
+    name: "async_start_bugreport"
+    namespace: "crumpet"
+    description: "Don't block callers on the start of dumpsys service"
+    bug: "180123623"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index 9ba88aa..fe774aa 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -504,9 +504,12 @@
         } else {
             storageFlags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
         }
-        List<String> deferPackages = reconcileAppsDataLI(StorageManager.UUID_PRIVATE_INTERNAL,
-                UserHandle.USER_SYSTEM, storageFlags, true /* migrateAppData */,
-                true /* onlyCoreApps */);
+        final List<String> deferPackages;
+        synchronized (mPm.mInstallLock) {
+           deferPackages = reconcileAppsDataLI(StorageManager.UUID_PRIVATE_INTERNAL,
+                    UserHandle.USER_SYSTEM, storageFlags, true /* migrateAppData */,
+                    true /* onlyCoreApps */);
+        }
         Future<?> prepareAppDataFuture = SystemServerInitThreadPool.submit(() -> {
             TimingsTraceLog traceLog = new TimingsTraceLog("SystemServerTimingAsync",
                     Trace.TRACE_TAG_PACKAGE_MANAGER);
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
index 3862b79..5ac883c 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -688,6 +688,29 @@
         );
     }
 
+    /** Call intent should be handled by the main user. */
+    private static final DefaultCrossProfileIntentFilter CALL_PRIVATE_PROFILE =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    SKIP_CURRENT_PROFILE,
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_CALL)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .addDataScheme("tel")
+                    .addDataScheme("sip")
+                    .addDataScheme("voicemail")
+                    .build();
+
+    /** Pressing the call button should be handled by the main user. */
+    private static final DefaultCrossProfileIntentFilter CALL_BUTTON_PRIVATE_PROFILE =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    ONLY_IF_NO_MATCH_FOUND,
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_CALL_BUTTON)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .build();
+
     /** Dial intent with mime type can be handled by either private profile or its parent user. */
     private static final DefaultCrossProfileIntentFilter DIAL_MIME_PRIVATE_PROFILE =
             new DefaultCrossProfileIntentFilter.Builder(
@@ -755,6 +778,10 @@
                 DIAL_MIME_PRIVATE_PROFILE,
                 DIAL_DATA_PRIVATE_PROFILE,
                 DIAL_RAW_PRIVATE_PROFILE,
+                CALL_PRIVATE_PROFILE,
+                CALL_BUTTON_PRIVATE_PROFILE,
+                EMERGENCY_CALL_DATA,
+                EMERGENCY_CALL_MIME,
                 SMS_MMS_PRIVATE_PROFILE
         );
     }
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index c60f0af..209cbb7 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -46,6 +46,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApexStagedEvent;
+import android.content.pm.Flags;
 import android.content.pm.IPackageManagerNative;
 import android.content.pm.IStagedApexObserver;
 import android.content.pm.PackageManager;
@@ -766,6 +767,10 @@
         final PackageSetting ps = installRequest.getScannedPackageSetting();
         final AndroidPackage pkg = ps.getPkg();
         final boolean onIncremental = isIncrementalPath(ps.getPathString());
+        final boolean performDexOptForRollback = Flags.recoverabilityDetection()
+                ? !(installRequest.isRollback()
+                && installRequest.getInstallSource().mInitiatingPackageName.equals("android"))
+                : true;
 
         return (!instantApp || Global.getInt(context.getContentResolver(),
                 Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
@@ -773,7 +778,8 @@
                 && !pkg.isDebuggable()
                 && (!onIncremental)
                 && dexoptOptions.isCompilationEnabled()
-                && !isApex;
+                && !isApex
+                && performDexOptForRollback;
     }
 
     private static class StagedApexObserver extends IStagedApexObserver.Stub {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 19a0ba7..472f228 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -37,7 +37,7 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_STAGED;
 import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
-import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL_PATH;
+import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL;
 import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
 import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4;
 import static android.content.pm.parsing.ApkLiteParseUtils.isApkFile;
@@ -505,6 +505,7 @@
         // metadata file path for the new package.
         if (oldPkgSetting != null) {
             pkgSetting.setAppMetadataFilePath(null);
+            pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_UNKNOWN);
         }
         // If the app metadata file path is not null then this is a system app with a preloaded app
         // metadata file on the system image. Do not reset the path and source if this is the
@@ -523,7 +524,7 @@
                 }
             } else if (Flags.aslInApkAppMetadataSource()) {
                 Map<String, PackageManager.Property> properties = pkg.getProperties();
-                if (properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL_PATH)) {
+                if (properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL)) {
                     // ASL file extraction is done in post-install
                     pkgSetting.setAppMetadataFilePath(appMetadataFile.getAbsolutePath());
                     pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_APK);
@@ -985,13 +986,13 @@
     }
 
     void installPackagesTraced(List<InstallRequest> requests) {
-        synchronized (mPm.mInstallLock) {
-            try {
-                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages");
-                installPackagesLI(requests);
-            } finally {
-                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-            }
+        mPm.mInstallLock.lock();
+        try {
+            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages");
+            installPackagesLI(requests);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+            mPm.mInstallLock.unlock();
         }
     }
 
@@ -2590,22 +2591,30 @@
             final boolean performDexopt = DexOptHelper.shouldPerformDexopt(installRequest,
                     dexoptOptions, mContext);
             if (performDexopt) {
-                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
+                // dexopt can take long, and ArtService doesn't require installd, so we release
+                // the lock here and re-acquire the lock after dexopt is finished.
+                mPm.mInstallLock.unlock();
+                try {
+                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
 
-                // This mirrors logic from commitReconciledScanResultLocked, where the library files
-                // needed for dexopt are assigned.
-                PackageSetting realPkgSetting = installRequest.getRealPackageSetting();
+                    // This mirrors logic from commitReconciledScanResultLocked, where the library
+                    // files needed for dexopt are assigned.
+                    PackageSetting realPkgSetting = installRequest.getRealPackageSetting();
 
-                // Unfortunately, the updated system app flag is only tracked on this PackageSetting
-                boolean isUpdatedSystemApp =
-                        installRequest.getScannedPackageSetting().isUpdatedSystemApp();
+                    // Unfortunately, the updated system app flag is only tracked on this
+                    // PackageSetting
+                    boolean isUpdatedSystemApp =
+                            installRequest.getScannedPackageSetting().isUpdatedSystemApp();
 
-                realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
+                    realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
 
-                DexoptResult dexOptResult =
-                        DexOptHelper.dexoptPackageUsingArtService(installRequest, dexoptOptions);
-                installRequest.onDexoptFinished(dexOptResult);
-                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+                    DexoptResult dexOptResult = DexOptHelper.dexoptPackageUsingArtService(
+                            installRequest, dexoptOptions);
+                    installRequest.onDexoptFinished(dexOptResult);
+                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+                } finally {
+                    mPm.mInstallLock.lock();
+                }
             }
         }
         PackageManagerServiceUtils.waitForNativeBinariesExtractionForIncremental(
diff --git a/services/core/java/com/android/server/pm/PackageFreezer.java b/services/core/java/com/android/server/pm/PackageFreezer.java
index 7c56157..0afda45 100644
--- a/services/core/java/com/android/server/pm/PackageFreezer.java
+++ b/services/core/java/com/android/server/pm/PackageFreezer.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.pm.Flags;
 import android.content.pm.PackageManager;
 
 import dalvik.system.CloseGuard;
@@ -76,8 +77,13 @@
             ps = mPm.mSettings.getPackageLPr(mPackageName);
         }
         if (ps != null) {
-            mPm.killApplication(ps.getPackageName(), ps.getAppId(), userId, killReason,
-                    exitInfoReason);
+            if (Flags.waitApplicationKilled()) {
+                mPm.killApplicationSync(ps.getPackageName(), ps.getAppId(), userId, killReason,
+                        exitInfoReason);
+            } else {
+                mPm.killApplication(ps.getPackageName(), ps.getAppId(), userId, killReason,
+                        exitInfoReason);
+            }
         }
         mCloseGuard.open("close");
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ae485ed..f8fceda 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -53,6 +53,7 @@
 import android.annotation.UserIdInt;
 import android.annotation.WorkerThread;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
 import android.app.ApplicationExitInfo;
 import android.app.ApplicationPackageManager;
@@ -626,7 +627,7 @@
     // Lock for state used when installing and doing other long running
     // operations.  Methods that must be called with this lock held have
     // the suffix "LI".
-    final Object mInstallLock;
+    final PackageManagerTracedLock mInstallLock;
 
     // ----------------------------------------------------------------
 
@@ -1692,8 +1693,8 @@
         final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
                 Trace.TRACE_TAG_PACKAGE_MANAGER);
         t.traceBegin("create package manager");
-        final PackageManagerTracedLock lock = new PackageManagerTracedLock();
-        final Object installLock = new Object();
+        final PackageManagerTracedLock lock = new PackageManagerTracedLock("mLock");
+        final PackageManagerTracedLock installLock = new PackageManagerTracedLock("mInstallLock");
 
         HandlerThread backgroundThread = new ServiceThread("PackageManagerBg",
                 Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
@@ -3132,6 +3133,20 @@
         }
     }
 
+    void killApplicationSync(String pkgName, @AppIdInt int appId,
+            @UserIdInt int userId, String reason, int exitInfoReason) {
+        ActivityManagerInternal mAmi = LocalServices.getService(ActivityManagerInternal.class);
+        if (mAmi != null) {
+            if (Thread.holdsLock(mLock)) {
+                // holds PM's lock, go back killApplication to avoid it run into watchdog reset.
+                Slog.e(TAG, "Holds PM's locker, unable kill application synchronized");
+                killApplication(pkgName, appId, userId, reason, exitInfoReason);
+            } else {
+                mAmi.killApplicationSync(pkgName, appId, userId, reason, exitInfoReason);
+            }
+        }
+    }
+
     @Override
     public void notifyPackageAdded(String packageName, int uid) {
         mPackageObserverHelper.notifyAdded(packageName, uid);
@@ -4003,7 +4018,7 @@
                 final PackageMetrics.ComponentStateMetrics componentStateMetrics =
                         new PackageMetrics.ComponentStateMetrics(setting,
                                 UserHandle.getUid(userId, packageSetting.getAppId()),
-                                packageSetting.getEnabled(userId));
+                                packageSetting.getEnabled(userId), callingUid);
                 if (!setEnabledSettingInternalLocked(computer, packageSetting, setting, userId,
                         callingPackage)) {
                     continue;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index 83f3b16..ae2eaeb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -86,7 +86,7 @@
     private final Context mContext;
     private final PackageManagerTracedLock mLock;
     private final Installer mInstaller;
-    private final Object mInstallLock;
+    private final PackageManagerTracedLock mInstallLock;
     private final Handler mBackgroundHandler;
     private final Executor mBackgroundExecutor;
     private final List<ScanPartition> mSystemPartitions;
@@ -144,7 +144,7 @@
     private final Singleton<PackageMonitorCallbackHelper> mPackageMonitorCallbackHelper;
 
     PackageManagerServiceInjector(Context context, PackageManagerTracedLock lock,
-            Installer installer, Object installLock, PackageAbiHelper abiHelper,
+            Installer installer, PackageManagerTracedLock installLock, PackageAbiHelper abiHelper,
             Handler backgroundHandler,
             List<ScanPartition> systemPartitions,
             Producer<ComponentResolver> componentResolverProducer,
@@ -254,7 +254,7 @@
         return mAbiHelper;
     }
 
-    public Object getInstallLock() {
+    public PackageManagerTracedLock getInstallLock() {
         return mInstallLock;
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index fa54f6e..23ae983 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -19,7 +19,7 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
-import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL_PATH;
+import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL;
 import static android.content.pm.SigningDetails.CertCapabilities.SHARED_USER_ID;
 import static android.system.OsConstants.O_CREAT;
 import static android.system.OsConstants.O_RDWR;
@@ -71,8 +71,12 @@
 import android.content.pm.parsing.PackageLite;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.res.ApkAssets;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
 import android.os.Binder;
 import android.os.Build;
+import android.os.CancellationSignal;
 import android.os.Debug;
 import android.os.Environment;
 import android.os.FileUtils;
@@ -93,6 +97,7 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Base64;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.LogPrinter;
 import android.util.Printer;
@@ -147,11 +152,10 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.zip.GZIPInputStream;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
 
 /**
  * Class containing helper methods for the PackageManagerService.
@@ -1667,45 +1671,63 @@
         if (appMetadataFile.exists()) {
             return true;
         }
-        if (isSystem) {
+        Map<String, Property> properties = pkg.getProperties();
+        if (!properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL)) {
+            return false;
+        }
+        Property fileInApkProperty = properties.get(PROPERTY_ANDROID_SAFETY_LABEL);
+        if (!fileInApkProperty.isResourceId()) {
+            return false;
+        }
+        if (isSystem && !appMetadataFile.getParentFile().exists()) {
             try {
-                makeDirRecursive(new File(appMetadataFilePath).getParentFile(), 0700);
+                makeDirRecursive(appMetadataFile.getParentFile(), 0700);
             } catch (Exception e) {
                 Slog.e(TAG, "Failed to create app metadata dir for package "
                         + pkg.getPackageName() + ": " + e.getMessage());
                 return false;
             }
         }
-        Map<String, Property> properties = pkg.getProperties();
-        if (!properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL_PATH)) {
-            return false;
-        }
-        Property fileInAPkPathProperty = properties.get(PROPERTY_ANDROID_SAFETY_LABEL_PATH);
-        if (!fileInAPkPathProperty.isString()) {
-            return false;
-        }
-        String fileInApkPath = fileInAPkPathProperty.getString();
         List<AndroidPackageSplit> splits = pkg.getSplits();
+        AssetManager.Builder builder = new AssetManager.Builder();
         for (int i = 0; i < splits.size(); i++) {
-            try (ZipFile zipFile = new ZipFile(splits.get(i).getPath())) {
-                ZipEntry zipEntry = zipFile.getEntry(fileInApkPath);
-                if (zipEntry != null
-                        && (isSystem || zipEntry.getSize() <= getAppMetadataSizeLimit())) {
-                    try (InputStream in = zipFile.getInputStream(zipEntry)) {
-                        try (FileOutputStream out = new FileOutputStream(appMetadataFile)) {
-                            FileUtils.copy(in, out);
-                            Os.chmod(appMetadataFile.getAbsolutePath(),
-                                    APP_METADATA_FILE_ACCESS_MODE);
-                            return true;
+            try {
+                builder.addApkAssets(ApkAssets.loadFromPath(splits.get(i).getPath()));
+            } catch (IOException e) {
+                Slog.e(TAG, "Failed to load resources from APK " + splits.get(i).getPath());
+            }
+        }
+        AssetManager assetManager = builder.build();
+        DisplayMetrics displayMetrics = new DisplayMetrics();
+        displayMetrics.setToDefaults();
+        Resources res = new Resources(assetManager, displayMetrics, null);
+        AtomicBoolean copyFailed = new AtomicBoolean(false);
+        try (InputStream in = res.openRawResource(fileInApkProperty.getResourceId())) {
+            try (FileOutputStream out = new FileOutputStream(appMetadataFile)) {
+                if (isSystem) {
+                    FileUtils.copy(in, out);
+                } else {
+                    long sizeLimit = getAppMetadataSizeLimit();
+                    CancellationSignal signal = new CancellationSignal();
+                    FileUtils.copy(in, out, signal, Runnable::run, (long progress) -> {
+                        if (progress > sizeLimit) {
+                            copyFailed.set(true);
+                            signal.cancel();
                         }
-                    }
+                    });
                 }
-            } catch (Exception e) {
-                Slog.e(TAG, e.getMessage());
+                Os.chmod(appMetadataFile.getAbsolutePath(),
+                        APP_METADATA_FILE_ACCESS_MODE);
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, e.getMessage());
+            copyFailed.set(true);
+        } finally {
+            if (copyFailed.get()) {
                 appMetadataFile.delete();
             }
         }
-        return false;
+        return !copyFailed.get();
     }
 
     public static void linkFilesToOldDirs(@NonNull Installer installer,
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 1793794..0a8b2b2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -35,6 +35,8 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.role.RoleManager;
+import android.app.usage.StorageStats;
+import android.app.usage.StorageStatsManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.IIntentReceiver;
@@ -136,6 +138,7 @@
 import java.io.PrintWriter;
 import java.net.URISyntaxException;
 import java.security.SecureRandom;
+import java.text.DecimalFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Base64;
@@ -275,6 +278,8 @@
                     return runClear();
                 case "get-archived-package-metadata":
                     return runGetArchivedPackageMetadata();
+                case "get-package-storage-stats":
+                    return runGetPackageStorageStats();
                 case "install-archived":
                     return runArchivedInstall();
                 case "enable":
@@ -323,6 +328,8 @@
                     return runGetPrivappDenyPermissions();
                 case "get-oem-permissions":
                     return runGetOemPermissions();
+                case "get-signature-permission-allowlist":
+                    return runGetSignaturePermissionAllowlist();
                 case "trim-caches":
                     return runTrimCaches();
                 case "create-user":
@@ -1861,6 +1868,103 @@
         return 0;
     }
 
+    /**
+     * Returns a string that shows the number of bytes in b, Kb, Mb or Gb.
+     */
+    protected static String getFormattedBytes(long size) {
+        double k = size/1024.0;
+        double m = size/1048576.0;
+        double g = size/1073741824.0;
+
+        DecimalFormat dec = new DecimalFormat("0.00");
+        if (g > 1) {
+            return dec.format(g).concat(" Gb");
+        } else if (m > 1) {
+            return dec.format(m).concat(" Mb");
+        } else if (k > 1) {
+            return dec.format(k).concat(" Kb");
+        }
+        return "";
+    }
+
+    /**
+     * Return the string that displays the data size.
+     */
+    private String getDataSizeDisplay(long size) {
+        String formattedOutput = getFormattedBytes(size);
+        if (!formattedOutput.isEmpty()) {
+           formattedOutput = " (" + formattedOutput + ")";
+        }
+        return Long.toString(size) + " bytes" + formattedOutput;
+    }
+
+    /**
+     * Display storage stats of the specified package.
+     *
+     * Usage: get-package-storage-stats [--usr USER_ID] PACKAGE
+     */
+    private int runGetPackageStorageStats() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        if (!android.content.pm.Flags.getPackageStorageStats()) {
+            pw.println("Error: get_package_storage_stats flag is not enabled");
+            return 1;
+        }
+        if (!android.app.usage.Flags.getAppBytesByDataTypeApi()) {
+            pw.println("Error: get_app_bytes_by_data_type_api flag is not enabled");
+            return 1;
+        }
+        int userId = UserHandle.USER_CURRENT;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        final String packageName = getNextArg();
+        if (packageName == null) {
+            pw.println("Error: package name not specified");
+            return 1;
+        }
+        try {
+            StorageStatsManager storageStatsManager =
+                mContext.getSystemService(StorageStatsManager.class);
+            final int translatedUserId = translateUserId(userId, UserHandle.USER_NULL,
+                "runGetPackageStorageStats");
+            StorageStats stats =
+                storageStatsManager.queryStatsForPackage(StorageManager.UUID_DEFAULT,
+                    packageName, UserHandle.of(translatedUserId));
+
+            pw.println("code: " + getDataSizeDisplay(stats.getAppBytes()));
+            pw.println("data: " + getDataSizeDisplay(stats.getDataBytes()));
+            pw.println("cache: " + getDataSizeDisplay(stats.getCacheBytes()));
+            pw.println("apk: " + getDataSizeDisplay(stats.getAppBytesByDataType(
+                StorageStats.APP_DATA_TYPE_FILE_TYPE_APK)));
+            pw.println("lib: " + getDataSizeDisplay(
+                stats.getAppBytesByDataType(StorageStats.APP_DATA_TYPE_LIB)));
+            pw.println("dm: " + getDataSizeDisplay(stats.getAppBytesByDataType(
+                StorageStats.APP_DATA_TYPE_FILE_TYPE_DM)));
+            pw.println("dexopt artifacts: " + getDataSizeDisplay(stats.getAppBytesByDataType(
+                StorageStats.APP_DATA_TYPE_FILE_TYPE_DEXOPT_ARTIFACT)));
+            pw.println("current profile : " + getDataSizeDisplay(stats.getAppBytesByDataType(
+                StorageStats.APP_DATA_TYPE_FILE_TYPE_CURRENT_PROFILE)));
+            pw.println("reference profile: " + getDataSizeDisplay(stats.getAppBytesByDataType(
+                StorageStats.APP_DATA_TYPE_FILE_TYPE_REFERENCE_PROFILE)));
+            pw.println("external cache: " + getDataSizeDisplay(stats.getExternalCacheBytes()));
+        } catch (Exception e) {
+            getErrPrintWriter().println("Failed to get storage stats, reason: " + e);
+            pw.println("Failure [failed to get storage stats], reason: " + e);
+            return -1;
+        }
+        return 0;
+    }
+
     private int runInstallExisting() throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
         int userId = UserHandle.USER_CURRENT;
@@ -2818,6 +2922,54 @@
         return 0;
     }
 
+    private int runGetSignaturePermissionAllowlist() {
+        final var partition = getNextArg();
+        if (partition == null) {
+            getErrPrintWriter().println("Error: no partition specified.");
+            return 1;
+        }
+        final var permissionAllowlist =
+                SystemConfig.getInstance().getPermissionAllowlist();
+        final ArrayMap<String, ArrayMap<String, Boolean>> allowlist;
+        switch (partition) {
+            case "system":
+                allowlist = permissionAllowlist.getSignatureAppAllowlist();
+                break;
+            case "vendor":
+                allowlist = permissionAllowlist.getVendorSignatureAppAllowlist();
+                break;
+            case "product":
+                allowlist = permissionAllowlist.getProductSignatureAppAllowlist();
+                break;
+            case "system-ext":
+                allowlist = permissionAllowlist.getSystemExtSignatureAppAllowlist();
+                break;
+            default:
+                getErrPrintWriter().println("Error: unknown partition: " + partition);
+                return 1;
+        }
+        final var ipw = new IndentingPrintWriter(getOutPrintWriter(), "  ");
+        final var allowlistSize = allowlist.size();
+        for (var allowlistIndex = 0; allowlistIndex < allowlistSize; allowlistIndex++) {
+            final var packageName = allowlist.keyAt(allowlistIndex);
+            final var permissions = allowlist.valueAt(allowlistIndex);
+            ipw.print("Package: ");
+            ipw.println(packageName);
+            ipw.increaseIndent();
+            final var permissionsSize = permissions.size();
+            for (var permissionsIndex = 0; permissionsIndex < permissionsSize; permissionsIndex++) {
+                final var permissionName = permissions.keyAt(permissionsIndex);
+                final var granted = permissions.valueAt(permissionsIndex);
+                if (granted) {
+                    ipw.print("Permission: ");
+                    ipw.println(permissionName);
+                }
+            }
+            ipw.decreaseIndent();
+        }
+        return 0;
+    }
+
     private int runTrimCaches() throws RemoteException {
         String size = getNextArg();
         if (size == null) {
@@ -4750,6 +4902,10 @@
         pw.println("  get-oem-permissions TARGET-PACKAGE");
         pw.println("    Prints all OEM permissions for a package.");
         pw.println("");
+        pw.println("  get-signature-permission-allowlist PARTITION");
+        pw.println("    Prints the signature permission allowlist for a partition.");
+        pw.println("    PARTITION is one of system, vendor, product and system-ext");
+        pw.println("");
         pw.println("  trim-caches DESIRED_FREE_SPACE [internal|UUID]");
         pw.println("    Trim cache files to reach the given free space.");
         pw.println("");
@@ -4869,6 +5025,8 @@
         pw.println("    Displays the component name of the domain verification agent on device.");
         pw.println("    If the component isn't enabled, an error message will be displayed.");
         pw.println("      --user: return the agent of the given user (SYSTEM_USER if unspecified)");
+        pw.println("  get-package-storage-stats [--user <USER_ID>] <PACKAGE>");
+        pw.println("    Return the storage stats for the given app, if present");
         pw.println("");
         printArtServiceHelp();
         pw.println("");
diff --git a/services/core/java/com/android/server/pm/PackageManagerTracedLock.java b/services/core/java/com/android/server/pm/PackageManagerTracedLock.java
index 75e1803f..303b8b9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerTracedLock.java
+++ b/services/core/java/com/android/server/pm/PackageManagerTracedLock.java
@@ -16,6 +16,9 @@
 
 package com.android.server.pm;
 
+import android.annotation.Nullable;
+import android.util.Slog;
+
 import java.util.concurrent.locks.ReentrantLock;
 
 /**
@@ -23,4 +26,31 @@
  * injection, similar to {@link ActivityManagerGlobalLock}.
  */
 public class PackageManagerTracedLock extends ReentrantLock {
+    private static final String TAG = "PackageManagerTracedLock";
+    private static final boolean DEBUG = false;
+    @Nullable private final String mLockName;
+
+    public PackageManagerTracedLock(@Nullable String lockName) {
+        mLockName = lockName;
+    }
+
+    public PackageManagerTracedLock() {
+        this(null);
+    }
+
+    @Override
+    public void lock() {
+        super.lock();
+        if (DEBUG && mLockName != null) {
+            Slog.i(TAG, "locked " + mLockName);
+        }
+    }
+
+    @Override
+    public void unlock() {
+        super.unlock();
+        if (DEBUG && mLockName != null) {
+            Slog.i(TAG, "unlocked " + mLockName);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 20598f9..2081f73 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -358,6 +358,7 @@
 
     public static class ComponentStateMetrics {
         public int mUid;
+        public int mCallingUid;
         public int mComponentOldState;
         public int mComponentNewState;
         public boolean mIsForWholeApp;
@@ -365,13 +366,14 @@
         @Nullable private String mClassName;
 
         ComponentStateMetrics(@NonNull PackageManager.ComponentEnabledSetting setting, int uid,
-                int componentOldState) {
+                int componentOldState, int callingUid) {
             mUid = uid;
             mComponentOldState = componentOldState;
             mComponentNewState = setting.getEnabledState();
             mIsForWholeApp = !setting.isComponent();
             mPackageName = setting.getPackageName();
             mClassName = setting.getClassName();
+            mCallingUid = callingUid;
         }
 
         public boolean isSameComponent(ActivityInfo activityInfo) {
@@ -412,14 +414,15 @@
                     componentStateMetrics.mComponentOldState,
                     componentStateMetrics.mComponentNewState,
                     isLauncher,
-                    componentStateMetrics.mIsForWholeApp);
+                    componentStateMetrics.mIsForWholeApp,
+                    componentStateMetrics.mCallingUid);
         }
     }
 
     private static void reportComponentStateChanged(int uid, int componentOldState,
-            int componentNewState, boolean isLauncher, boolean isForWholeApp) {
+            int componentNewState, boolean isLauncher, boolean isForWholeApp, int callingUid) {
         FrameworkStatsLog.write(FrameworkStatsLog.COMPONENT_STATE_CHANGED_REPORTED,
-                uid, componentOldState, componentNewState, isLauncher, isForWholeApp);
+                uid, componentOldState, componentNewState, isLauncher, isForWholeApp, callingUid);
     }
 
     private static List<ResolveInfo> getHomeActivitiesResolveInfoAsUser(@NonNull Computer computer,
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 3a0f7fb..02bd3cc 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -376,11 +376,14 @@
             @NonNull PackageRemovedInfo outInfo, int flags, boolean writeSettings) {
         String packageName = deletedPs.getPackageName();
         if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + deletedPs);
+        final boolean shouldDeletePackageSetting =
+                shouldDeletePackageSetting(deletedPs, targetUserId, allUserHandles, flags);
         // Retrieve object to delete permissions for shared user later on
         final AndroidPackage deletedPkg = deletedPs.getPkg();
 
         // Delete all the data and states related to this package.
-        clearPackageStateForUserLIF(deletedPs, targetUserId, flags);
+        clearPackageStateForUserLIF(deletedPs,
+                shouldDeletePackageSetting ? UserHandle.USER_ALL : targetUserId, flags);
 
         // Delete from mPackages
         removePackageLI(packageName, (flags & PackageManager.DELETE_CHATTY) != 0);
@@ -392,7 +395,7 @@
             deletedPs.setPkg(null);
         }
 
-        if (shouldDeletePackageSetting(deletedPs, targetUserId, allUserHandles, flags)) {
+        if (shouldDeletePackageSetting) {
             // Delete from mSettings
             final SparseBooleanArray changedUsers = new SparseBooleanArray();
             synchronized (mPm.mLock) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 1309e44..8d6d774 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2139,10 +2139,18 @@
                     continue;
                 }
 
+                ComponentName unflattenOriginalComponentName = ComponentName.unflattenFromString(
+                        originalComponentName);
+                if (unflattenOriginalComponentName == null) {
+                    Slog.wtf(TAG, "Incorrect component name: " + originalComponentName
+                            + " from the attributes");
+                    continue;
+                }
+
                 activityInfos.add(
                         new ArchiveState.ArchiveActivityInfo(
                                 title,
-                                ComponentName.unflattenFromString(originalComponentName),
+                                unflattenOriginalComponentName,
                                 iconPath,
                                 monochromeIconPath));
             }
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 82902d4..9edf3b1 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -172,7 +172,7 @@
     static final boolean DEBUG = false; // STOPSHIP if true
     static final boolean DEBUG_LOAD = false; // STOPSHIP if true
     static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
-    static final boolean DEBUG_REBOOT = false; // STOPSHIP if true
+    static final boolean DEBUG_REBOOT = true;
 
     @VisibleForTesting
     static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
@@ -3798,24 +3798,36 @@
                 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
                 final boolean archival = intent.getBooleanExtra(Intent.EXTRA_ARCHIVAL, false);
 
+                Slog.d(TAG, "received package broadcast intent: " + intent);
                 switch (action) {
                     case Intent.ACTION_PACKAGE_ADDED:
                         if (replacing) {
+                            Slog.d(TAG, "replacing package: " + packageName + " userId" + userId);
                             handlePackageUpdateFinished(packageName, userId);
                         } else {
+                            Slog.d(TAG, "adding package: " + packageName + " userId" + userId);
                             handlePackageAdded(packageName, userId);
                         }
                         break;
                     case Intent.ACTION_PACKAGE_REMOVED:
                         if (!replacing || (replacing && archival)) {
+                            if (!replacing) {
+                                Slog.d(TAG, "removing package: "
+                                        + packageName + " userId" + userId);
+                            } else if (archival) {
+                                Slog.d(TAG, "archiving package: "
+                                        + packageName + " userId" + userId);
+                            }
                             handlePackageRemoved(packageName, userId);
                         }
                         break;
                     case Intent.ACTION_PACKAGE_CHANGED:
+                        Slog.d(TAG, "changing package: " + packageName + " userId" + userId);
                         handlePackageChanged(packageName, userId);
-
                         break;
                     case Intent.ACTION_PACKAGE_DATA_CLEARED:
+                        Slog.d(TAG, "clearing data for package: "
+                                + packageName + " userId" + userId);
                         handlePackageDataCleared(packageName, userId);
                         break;
                 }
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index 1d41401..ef32485 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -52,11 +52,11 @@
     private static final String TAG = "UserDataPreparer";
     private static final String XATTR_SERIAL = "user.serial";
 
-    private final Object mInstallLock;
+    private final PackageManagerTracedLock mInstallLock;
     private final Context mContext;
     private final Installer mInstaller;
 
-    UserDataPreparer(Installer installer, Object installLock, Context context) {
+    UserDataPreparer(Installer installer, PackageManagerTracedLock installLock, Context context) {
         mInstallLock = installLock;
         mContext = context;
         mInstaller = installer;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 3c702b4..ebdca5b 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -600,8 +600,11 @@
         public void onReceive(Context context, Intent intent) {
             if (isAutoLockForPrivateSpaceEnabled()) {
                 if (ACTION_SCREEN_OFF.equals(intent.getAction())) {
+                    Slog.d(LOG_TAG, "SCREEN_OFF broadcast received");
                     maybeScheduleMessageToAutoLockPrivateSpace();
                 } else if (ACTION_SCREEN_ON.equals(intent.getAction())) {
+                    Slog.d(LOG_TAG, "SCREEN_ON broadcast received, "
+                            + "removing queued message to auto-lock private space");
                     // Remove any queued messages since the device is interactive again
                     mHandler.removeCallbacksAndMessages(PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN);
                 }
@@ -619,6 +622,8 @@
                         getMainUserIdUnchecked());
         if (privateSpaceAutoLockPreference
                 != Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY) {
+            Slogf.d(LOG_TAG, "Not scheduling auto-lock on inactivity,"
+                    + "preference is set to %d", privateSpaceAutoLockPreference);
             return;
         }
         int privateProfileUserId = getPrivateProfileUserId();
@@ -632,6 +637,7 @@
     @VisibleForTesting
     void scheduleMessageToAutoLockPrivateSpace(int userId, Object token,
             long delayInMillis) {
+        Slog.i(LOG_TAG, "Scheduling auto-lock message");
         mHandler.postDelayed(() -> {
             final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
             if (powerManager != null && !powerManager.isInteractive()) {
@@ -1060,8 +1066,6 @@
         if (isAutoLockingPrivateSpaceOnRestartsEnabled()) {
             autoLockPrivateSpace();
         }
-
-        markEphemeralUsersForRemoval();
     }
 
     private boolean isAutoLockingPrivateSpaceOnRestartsEnabled() {
@@ -1098,21 +1102,6 @@
         }
     }
 
-    /** Marks all ephemeral users as slated for deletion. **/
-    private void markEphemeralUsersForRemoval() {
-        synchronized (mUsersLock) {
-            final int userSize = mUsers.size();
-            for (int i = 0; i < userSize; i++) {
-                final UserInfo ui = mUsers.valueAt(i).info;
-                if (ui.isEphemeral() && !ui.preCreated && ui.id != UserHandle.USER_SYSTEM) {
-                    addRemovingUserIdLocked(ui.id);
-                    ui.partial = true;
-                    ui.flags |= UserInfo.FLAG_DISABLED;
-                }
-            }
-        }
-    }
-
     /* Prunes out any partially created or partially removed users. */
     private void cleanupPartialUsers() {
         ArrayList<UserInfo> partials = new ArrayList<>();
@@ -1935,16 +1924,20 @@
     private void showConfirmCredentialToDisableQuietMode(
             @UserIdInt int userId, @Nullable IntentSender target, @Nullable String callingPackage) {
         if (android.app.admin.flags.Flags.quietModeCredentialBugFix()) {
-            // TODO (b/308121702) It may be brittle to rely on user states to check profile state
-            int state;
-            synchronized (mUserStates) {
-                state = mUserStates.get(userId, UserState.STATE_NONE);
-            }
-            if (state != UserState.STATE_NONE) {
-                Slog.i(LOG_TAG,
-                        "showConfirmCredentialToDisableQuietMode() called too early, user " + userId
-                                + " is still alive.");
-                return;
+            if (!android.multiuser.Flags.restrictQuietModeCredentialBugFixToManagedProfiles()
+                    || getUserInfo(userId).isManagedProfile()) {
+                // TODO (b/308121702) It may be brittle to rely on user states to check managed
+                //  profile state
+                int state;
+                synchronized (mUserStates) {
+                    state = mUserStates.get(userId, UserState.STATE_NONE);
+                }
+                if (state != UserState.STATE_NONE) {
+                    Slog.i(LOG_TAG,
+                            "showConfirmCredentialToDisableQuietMode() called too early, managed "
+                                    + "user " + userId + " is still alive.");
+                    return;
+                }
             }
         }
         // otherwise, we show a profile challenge to trigger decryption of the user
@@ -4223,6 +4216,13 @@
                                             || mNextSerialNumber <= userData.info.id) {
                                         mNextSerialNumber = userData.info.id + 1;
                                     }
+                                    if (userData.info.isEphemeral() && !userData.info.preCreated
+                                            && userData.info.id != UserHandle.USER_SYSTEM) {
+                                        // Mark ephemeral user as slated for deletion.
+                                        addRemovingUserIdLocked(userData.info.id);
+                                        userData.info.partial = true;
+                                        userData.info.flags |= UserInfo.FLAG_DISABLED;
+                                    }
                                 }
                             }
                         } else if (name.equals(TAG_GUEST_RESTRICTIONS)) {
@@ -7155,6 +7155,7 @@
         synchronized (mUsersLock) {
             pw.println("  Boot user: " + mBootUser);
         }
+        pw.println("Can add private profile: "+ canAddPrivateProfile(currentUserId));
 
         pw.println();
         pw.println("Number of listeners for");
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 4e02470..483d308 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -859,7 +859,6 @@
                 break;
 
             case android.provider.Settings.System.SCREEN_BRIGHTNESS:
-            case android.provider.Settings.System.SCREEN_BRIGHTNESS_FLOAT:
             case android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE:
                 if (callingUid == Process.SYSTEM_UID) {
                     return false;
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index 1a9e012..f7eb29f 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -141,6 +141,8 @@
     @NonNull
     private final PackageManagerService mPm;
 
+    private final int mInstallReason;
+
     VerifyingSession(UserHandle user, File stagedDir, IPackageInstallObserver2 observer,
             PackageInstaller.SessionParams sessionParams, InstallSource installSource,
             int installerUid, SigningDetails signingDetails, int sessionId, PackageLite lite,
@@ -168,6 +170,7 @@
         mUserActionRequiredType = sessionParams.requireUserAction;
         mIsInherit = sessionParams.mode == MODE_INHERIT_EXISTING;
         mIsStaged = sessionParams.isStaged;
+        mInstallReason = sessionParams.installReason;
     }
 
     @Override
@@ -190,7 +193,9 @@
         // Perform package verification and enable rollback (unless we are simply moving the
         // package).
         if (!mOriginInfo.mExisting) {
-            if (!isApex() && !isArchivedInstallation()) {
+            final boolean verifyForRollback = Flags.recoverabilityDetection()
+                    ? !isARollback() : true;
+            if (!isApex() && !isArchivedInstallation() && verifyForRollback) {
                 // TODO(b/182426975): treat APEX as APK when APK verification is concerned
                 sendApkVerificationRequest(pkgLite);
             }
@@ -200,6 +205,11 @@
         }
     }
 
+    private boolean isARollback() {
+        return mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK
+                && mInstallSource.mInitiatingPackageName.equals("android");
+    }
+
     private void sendApkVerificationRequest(PackageInfoLite pkgLite) {
         final int verificationId = mPm.mPendingVerificationToken++;
 
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 5fa8856..11b9e77 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -64,6 +64,7 @@
 import com.android.server.input.InputManagerInternal;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.power.feature.PowerManagerFlags;
 import com.android.server.statusbar.StatusBarManagerInternal;
 
 import java.io.PrintWriter;
@@ -187,11 +188,16 @@
 
     private final AtomicBoolean mIsPlayingChargingStartedFeedback = new AtomicBoolean(false);
 
+    private final Injector mInjector;
+
+    private final PowerManagerFlags mFlags;
+
     public Notifier(Looper looper, Context context, IBatteryStats batteryStats,
             SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
             FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector,
-            Executor backgroundExecutor) {
+            Executor backgroundExecutor, PowerManagerFlags powerManagerFlags, Injector injector) {
         mContext = context;
+        mFlags = powerManagerFlags;
         mBatteryStats = batteryStats;
         mAppOps = mContext.getSystemService(AppOpsManager.class);
         mSuspendBlocker = suspendBlocker;
@@ -224,8 +230,8 @@
         mShowWirelessChargingAnimationConfig = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim);
 
-        mWakeLockLog = new WakeLockLog(context);
-
+        mInjector = (injector == null) ? new RealInjector() : injector;
+        mWakeLockLog = mInjector.getWakeLockLog(context);
         // Initialize interactive state for battery stats.
         try {
             mBatteryStats.noteInteractive(true);
@@ -267,7 +273,7 @@
                     + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
                     + ", workSource=" + workSource);
         }
-        notifyWakeLockListener(callback, tag, true);
+        notifyWakeLockListener(callback, tag, true, ownerUid, flags);
         final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
         if (monitorType >= 0) {
             try {
@@ -287,8 +293,9 @@
             }
         }
 
-        mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags);
-
+        if (!mFlags.improveWakelockLatency()) {
+            mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags, /*eventTime=*/ -1);
+        }
         mWakefulnessSessionObserver.onWakeLockAcquired(flags);
     }
 
@@ -406,7 +413,7 @@
                     + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
                     + ", workSource=" + workSource);
         }
-        notifyWakeLockListener(callback, tag, false);
+        notifyWakeLockListener(callback, tag, false, ownerUid, flags);
         final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
         if (monitorType >= 0) {
             try {
@@ -422,8 +429,9 @@
                 // Ignore
             }
         }
-        mWakeLockLog.onWakeLockReleased(tag, ownerUid);
-
+        if (!mFlags.improveWakelockLatency()) {
+            mWakeLockLog.onWakeLockReleased(tag, ownerUid, /*eventTime=*/ -1);
+        }
         mWakefulnessSessionObserver.onWakeLockReleased(flags, releaseReason);
     }
 
@@ -1040,10 +1048,19 @@
         return enabled && dndOff;
     }
 
-    private void notifyWakeLockListener(IWakeLockCallback callback, String tag, boolean isEnabled) {
+    private void notifyWakeLockListener(IWakeLockCallback callback, String tag, boolean isEnabled,
+            int ownerUid, int flags) {
         if (callback != null) {
+            long currentTime = mInjector.currentTimeMillis();
             mHandler.post(() -> {
                 try {
+                    if (mFlags.improveWakelockLatency()) {
+                        if (isEnabled) {
+                            mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags, currentTime);
+                        } else {
+                            mWakeLockLog.onWakeLockReleased(tag, ownerUid, currentTime);
+                        }
+                    }
                     callback.onStateChanged(isEnabled);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Wakelock.mCallback [" + tag + "] is already dead.", e);
@@ -1057,6 +1074,7 @@
         public NotifierHandler(Looper looper) {
             super(looper, null, true /*async*/);
         }
+
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -1085,4 +1103,28 @@
             }
         }
     }
+
+    public interface Injector {
+        /**
+         * Gets the current time in millis
+         */
+        long currentTimeMillis();
+
+        /**
+         * Gets the WakeLockLog object
+         */
+        WakeLockLog getWakeLockLog(Context context);
+    }
+
+    static class RealInjector implements Injector {
+        @Override
+        public long currentTimeMillis() {
+            return System.currentTimeMillis();
+        }
+
+        @Override
+        public WakeLockLog getWakeLockLog(Context context) {
+            return new WakeLockLog(context);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 76cedd8..ce0120c 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -988,10 +988,10 @@
         Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
                 SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
                 FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector,
-                Executor backgroundExecutor) {
+                Executor backgroundExecutor, PowerManagerFlags powerManagerFlags) {
             return new Notifier(
                     looper, context, batteryStats, suspendBlocker, policy, faceDownDetector,
-                    screenUndimDetector, backgroundExecutor);
+                    screenUndimDetector, backgroundExecutor, powerManagerFlags, /*injector=*/ null);
         }
 
         SuspendBlocker createSuspendBlocker(PowerManagerService service, String name) {
@@ -1373,7 +1373,7 @@
             mNotifier = mInjector.createNotifier(Looper.getMainLooper(), mContext, mBatteryStats,
                     mInjector.createSuspendBlocker(this, "PowerManagerService.Broadcasts"),
                     mPolicy, mFaceDownDetector, mScreenUndimDetector,
-                    BackgroundThread.getExecutor());
+                    BackgroundThread.getExecutor(), mFeatureFlags);
 
             mPowerGroups.append(Display.DEFAULT_DISPLAY_GROUP,
                     new PowerGroup(WAKEFULNESS_AWAKE, mPowerGroupWakefulnessChangeListener,
diff --git a/services/core/java/com/android/server/power/WakeLockLog.java b/services/core/java/com/android/server/power/WakeLockLog.java
index b131311..968ff59 100644
--- a/services/core/java/com/android/server/power/WakeLockLog.java
+++ b/services/core/java/com/android/server/power/WakeLockLog.java
@@ -154,9 +154,10 @@
      * @param tag The wake lock tag
      * @param ownerUid The owner UID of the wake lock.
      * @param flags Flags used for the wake lock.
+     * @param eventTime The time at which the event occurred
      */
-    public void onWakeLockAcquired(String tag, int ownerUid, int flags) {
-        onWakeLockEvent(TYPE_ACQUIRE, tag, ownerUid, flags);
+    public void onWakeLockAcquired(String tag, int ownerUid, int flags, long eventTime) {
+        onWakeLockEvent(TYPE_ACQUIRE, tag, ownerUid, flags, eventTime);
     }
 
     /**
@@ -164,9 +165,10 @@
      *
      * @param tag The wake lock tag
      * @param ownerUid The owner UID of the wake lock.
+     * @param eventTime The time at which the event occurred
      */
-    public void onWakeLockReleased(String tag, int ownerUid) {
-        onWakeLockEvent(TYPE_RELEASE, tag, ownerUid, 0 /* flags */);
+    public void onWakeLockReleased(String tag, int ownerUid, long eventTime) {
+        onWakeLockEvent(TYPE_RELEASE, tag, ownerUid, 0 /* flags */, eventTime);
     }
 
     /**
@@ -242,9 +244,10 @@
      * @param tag The wake lock's identifying tag.
      * @param ownerUid The owner UID of the wake lock.
      * @param flags The flags used with the wake lock.
+     * @param eventTime The time at which the event occurred
      */
     private void onWakeLockEvent(int eventType, String tag, int ownerUid,
-            int flags) {
+            int flags, long eventTime) {
         if (tag == null) {
             Slog.w(TAG, "Insufficient data to log wakelock [tag: " + tag
                     + ", ownerUid: " + ownerUid
@@ -252,7 +255,8 @@
             return;
         }
 
-        final long time = mInjector.currentTimeMillis();
+        final long time = (eventTime == -1) ? mInjector.currentTimeMillis() : eventTime;
+
         final int translatedFlags = eventType == TYPE_ACQUIRE
                 ? translateFlagsFromPowerManager(flags)
                 : 0;
diff --git a/services/core/java/com/android/server/power/batterysaver/flags.aconfig b/services/core/java/com/android/server/power/batterysaver/flags.aconfig
index 059c4a4..1dea523 100644
--- a/services/core/java/com/android/server/power/batterysaver/flags.aconfig
+++ b/services/core/java/com/android/server/power/batterysaver/flags.aconfig
@@ -3,7 +3,7 @@
 
 flag {
   name: "update_auto_turn_on_notification_string_and_action"
-  namespace: "power_optimization"
+  namespace: "backstage_power"
   description: "Improve the string and hightligh settings item for battery saver auto-turn-on notification"
   bug: "336960905"
   metadata {
diff --git a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
index a5a7069..ff1d2e4 100644
--- a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
+++ b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
@@ -35,18 +35,31 @@
             Flags.FLAG_ENABLE_EARLY_SCREEN_TIMEOUT_DETECTOR,
             Flags::enableEarlyScreenTimeoutDetector);
 
+    private final FlagState mImproveWakelockLatency = new FlagState(
+            Flags.FLAG_IMPROVE_WAKELOCK_LATENCY,
+            Flags::improveWakelockLatency
+    );
+
     /** Returns whether early-screen-timeout-detector is enabled on not. */
     public boolean isEarlyScreenTimeoutDetectorEnabled() {
         return mEarlyScreenTimeoutDetectorFlagState.isEnabled();
     }
 
     /**
+     * @return Whether to improve the wakelock acquire/release latency or not
+     */
+    public boolean improveWakelockLatency() {
+        return mImproveWakelockLatency.isEnabled();
+    }
+
+    /**
      * dumps all flagstates
      * @param pw printWriter
      */
     public void dump(PrintWriter pw) {
         pw.println("PowerManagerFlags:");
         pw.println(" " + mEarlyScreenTimeoutDetectorFlagState);
+        pw.println(" " + mImproveWakelockLatency);
     }
 
     private static class FlagState {
diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig
index ca58153..3581b2f 100644
--- a/services/core/java/com/android/server/power/feature/power_flags.aconfig
+++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig
@@ -10,3 +10,11 @@
     bug: "309861917"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "improve_wakelock_latency"
+    namespace: "power"
+    description: "Feature flag for tracking the optimizations to improve the latency of acquiring and releasing a wakelock."
+    bug: "339590565"
+    is_fixed_read_only: true
+}
\ No newline at end of file
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 4ddd546..267ddd0 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.power.hint;
 
+import static android.os.Flags.adpfUseFmqChannel;
+
 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
 import static com.android.server.power.hint.Flags.powerhintThreadCleanup;
 
@@ -26,6 +28,8 @@
 import android.app.StatsManager;
 import android.app.UidObserver;
 import android.content.Context;
+import android.hardware.power.ChannelConfig;
+import android.hardware.power.IPower;
 import android.hardware.power.SessionConfig;
 import android.hardware.power.SessionTag;
 import android.hardware.power.WorkDuration;
@@ -39,6 +43,7 @@
 import android.os.PerformanceHintManager;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -69,6 +74,7 @@
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -91,9 +97,21 @@
     @GuardedBy("mLock")
     private final ArrayMap<Integer, ArrayMap<IBinder, ArraySet<AppHintSession>>> mActiveSessions;
 
+    // Multi-level map storing all the channel binder token death listeners.
+    // First level is keyed by the UID of the client process owning the channel.
+    // Second level is the tgid of the process, which will often just be size one.
+    // Each channel is unique per (tgid, uid) pair, so this map associates each pair with an
+    // object that listens for the death notification of the binder token that was provided by
+    // that client when it created the channel, so we can detect when the client process dies.
+    @GuardedBy("mChannelMapLock")
+    private ArrayMap<Integer, TreeMap<Integer, ChannelItem>> mChannelMap;
+
     /** Lock to protect mActiveSessions and the UidObserver. */
     private final Object mLock = new Object();
 
+    /** Lock to protect mChannelMap. */
+    private final Object mChannelMapLock = new Object();
+
     @GuardedBy("mNonIsolatedTidsLock")
     private final Map<Integer, Set<Long>> mNonIsolatedTids;
 
@@ -110,6 +128,9 @@
 
     private AtomicBoolean mConfigCreationSupport = new AtomicBoolean(true);
 
+    private final IPower mPowerHal;
+    private int mPowerHalVersion;
+
     private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint";
     private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager";
 
@@ -131,12 +152,22 @@
             mNonIsolatedTids = null;
         }
         mActiveSessions = new ArrayMap<>();
+        mChannelMap = new ArrayMap<>();
         mNativeWrapper = injector.createNativeWrapper();
         mNativeWrapper.halInit();
         mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate();
         mUidObserver = new MyUidObserver();
         mAmInternal = Objects.requireNonNull(
                 LocalServices.getService(ActivityManagerInternal.class));
+        mPowerHal = injector.createIPower();
+        mPowerHalVersion = 0;
+        if (mPowerHal != null) {
+            try {
+                mPowerHalVersion = mPowerHal.getInterfaceVersion();
+            } catch (RemoteException e) {
+                throw new IllegalStateException("Could not contact PowerHAL!", e);
+            }
+        }
     }
 
     private ServiceThread createCleanUpThread() {
@@ -151,6 +182,10 @@
         NativeWrapper createNativeWrapper() {
             return new NativeWrapper();
         }
+        IPower createIPower() {
+            return IPower.Stub.asInterface(
+                ServiceManager.waitForDeclaredService(IPower.DESCRIPTOR + "/default"));
+        }
     }
 
     private boolean isHalSupported() {
@@ -344,6 +379,16 @@
                         }
                     }
                 }
+                synchronized (mChannelMapLock) {
+                    // Clean up the uid's session channels
+                    final TreeMap<Integer, ChannelItem> uidMap = mChannelMap.get(uid);
+                    if (uidMap != null) {
+                        for (Map.Entry<Integer, ChannelItem> entry : uidMap.entrySet()) {
+                            entry.getValue().closeChannel();
+                        }
+                        mChannelMap.remove(uid);
+                    }
+                }
             });
         }
 
@@ -383,6 +428,113 @@
         }
     }
 
+    /**
+     * Creates a channel item in the channel map if one does not exist, then returns
+     * the entry in the channel map.
+     */
+    public ChannelItem getOrCreateMappedChannelItem(int tgid, int uid, IBinder token) {
+        synchronized (mChannelMapLock) {
+            if (!mChannelMap.containsKey(uid)) {
+                mChannelMap.put(uid, new TreeMap<Integer, ChannelItem>());
+            }
+            TreeMap<Integer, ChannelItem> map = mChannelMap.get(uid);
+            if (!map.containsKey(tgid)) {
+                ChannelItem item = new ChannelItem(tgid, uid, token);
+                item.openChannel();
+                map.put(tgid, item);
+            }
+            return map.get(tgid);
+        }
+    }
+
+    /**
+     * This removes an entry in the binder token callback map when a channel is closed,
+     * and unregisters its callbacks.
+     */
+    public void removeChannelItem(Integer tgid, Integer uid) {
+        synchronized (mChannelMapLock) {
+            TreeMap<Integer, ChannelItem> map = mChannelMap.get(uid);
+            if (map != null) {
+                ChannelItem item = map.get(tgid);
+                if (item != null) {
+                    item.closeChannel();
+                    map.remove(tgid);
+                }
+                if (map.isEmpty()) {
+                    mChannelMap.remove(uid);
+                }
+            }
+        }
+    }
+
+    /**
+     * Manages the lifecycle of a single channel. This includes caching the channel descriptor,
+     * receiving binder token death notifications, and handling cleanup on uid termination. There
+     * can only be one ChannelItem per (tgid, uid) pair in mChannelMap, and channel creation happens
+     * when a ChannelItem enters the map, while destruction happens when it leaves the map.
+     */
+    private class ChannelItem implements IBinder.DeathRecipient {
+        @Override
+        public void binderDied() {
+            removeChannelItem(mTgid, mUid);
+        }
+
+        ChannelItem(int tgid, int uid, IBinder token) {
+            this.mTgid = tgid;
+            this.mUid = uid;
+            this.mToken = token;
+            this.mLinked = false;
+            this.mConfig = null;
+        }
+
+        public void closeChannel() {
+            if (mLinked) {
+                mToken.unlinkToDeath(this, 0);
+                mLinked = false;
+            }
+            if (mConfig != null) {
+                try  {
+                    mPowerHal.closeSessionChannel(mTgid, mUid);
+                } catch (RemoteException e) {
+                    throw new IllegalStateException("Failed to close session channel!", e);
+                }
+                mConfig = null;
+            }
+        }
+
+        public void openChannel() {
+            if (!mLinked) {
+                try {
+                    mToken.linkToDeath(this, 0);
+                } catch (RemoteException e) {
+                    throw new IllegalStateException("Client already dead", e);
+                }
+                mLinked = true;
+            }
+            if (mConfig == null) {
+                try {
+                    // This method uses PowerHAL directly through the SDK,
+                    // to avoid needing to pass the ChannelConfig through JNI.
+                    mConfig = mPowerHal.getSessionChannel(mTgid, mUid);
+                } catch (RemoteException e) {
+                    removeChannelItem(mTgid, mUid);
+                    throw new IllegalStateException("Failed to create session channel!", e);
+                }
+            }
+        }
+
+        ChannelConfig getConfig() {
+            return mConfig;
+        }
+
+        // To avoid accidental double-linking / unlinking
+        boolean mLinked;
+        final int mTgid;
+        final int mUid;
+        final IBinder mToken;
+        ChannelConfig mConfig;
+    }
+
     final class CleanUpHandler extends Handler {
         // status of processed tid used for caching
         private static final int TID_NOT_CHECKED = 0;
@@ -570,6 +722,18 @@
         return mService;
     }
 
+    @VisibleForTesting
+    Boolean hasChannel(int tgid, int uid) {
+        synchronized (mChannelMapLock) {
+            TreeMap<Integer, ChannelItem> uidMap = mChannelMap.get(uid);
+            if (uidMap != null) {
+                ChannelItem item = uidMap.get(tgid);
+                return item != null;
+            }
+            return false;
+        }
+    }
+
     // returns the first invalid tid or null if not found
     private Integer checkTidValid(int uid, int tgid, int [] tids, IntArray nonIsolated) {
         // Make sure all tids belongs to the same UID (including isolated UID),
@@ -710,6 +874,28 @@
         }
 
         @Override
+        public ChannelConfig getSessionChannel(IBinder token) {
+            if (mPowerHalVersion < 5 || !adpfUseFmqChannel()) {
+                return null;
+            }
+            java.util.Objects.requireNonNull(token);
+            final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid());
+            final int callingUid = Binder.getCallingUid();
+            ChannelItem item = getOrCreateMappedChannelItem(callingTgid, callingUid, token);
+            return item.getConfig();
+        };
+
+        @Override
+        public void closeSessionChannel() {
+            if (mPowerHalVersion < 5 || !adpfUseFmqChannel()) {
+                return;
+            }
+            final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid());
+            final int callingUid = Binder.getCallingUid();
+            removeChannelItem(callingTgid, callingUid);
+        };
+
+        @Override
         public long getHintSessionPreferredRate() {
             return mHintSessionPreferredRate;
         }
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
index 5aad570..884c26c 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
@@ -19,12 +19,9 @@
 import android.annotation.NonNull;
 import android.os.BatteryConsumer;
 
-import com.android.internal.os.PowerStats;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -206,25 +203,9 @@
         return mPowerComponents;
     }
 
-    private static final PowerStatsProcessor NO_OP_PROCESSOR =
-            new PowerStatsProcessor() {
-                @Override
-                void finish(PowerComponentAggregatedPowerStats stats) {
-                }
-
-                @Override
-                String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
-                    return Arrays.toString(stats);
-                }
-
-                @Override
-                String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
-                    return descriptor.getStateLabel(key) + " " + Arrays.toString(stats);
-                }
-
-                @Override
-                String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
-                    return Arrays.toString(stats);
-                }
-            };
+    private static final PowerStatsProcessor NO_OP_PROCESSOR = new PowerStatsProcessor() {
+        @Override
+        void finish(PowerComponentAggregatedPowerStats stats) {
+        }
+    };
 }
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 49c4000..9a41551 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -463,11 +463,17 @@
     public static class BatteryStatsConfig {
         static final int RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG = 1 << 0;
         static final int RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG = 1 << 1;
-        static final long DEFAULT_POWER_STATS_COLLECTION_THROTTLE_PERIOD =
-                TimeUnit.HOURS.toMillis(1);
 
         private final int mFlags;
-        private SparseLongArray mPowerStatsThrottlePeriods;
+        private final Long mDefaultPowerStatsThrottlePeriod;
+        private final Map<String, Long> mPowerStatsThrottlePeriods;
+
+        @VisibleForTesting
+        public BatteryStatsConfig() {
+            mFlags = 0;
+            mDefaultPowerStatsThrottlePeriod = 0L;
+            mPowerStatsThrottlePeriods = Map.of();
+        }
 
         private BatteryStatsConfig(Builder builder) {
             int flags = 0;
@@ -478,6 +484,7 @@
                 flags |= RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG;
             }
             mFlags = flags;
+            mDefaultPowerStatsThrottlePeriod = builder.mDefaultPowerStatsThrottlePeriod;
             mPowerStatsThrottlePeriods = builder.mPowerStatsThrottlePeriods;
         }
 
@@ -485,7 +492,7 @@
          * Returns whether a BatteryStats reset should occur on unplug when the battery level is
          * high.
          */
-        boolean shouldResetOnUnplugHighBatteryLevel() {
+        public boolean shouldResetOnUnplugHighBatteryLevel() {
             return (mFlags & RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG)
                     == RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG;
         }
@@ -494,14 +501,18 @@
          * Returns whether a BatteryStats reset should occur on unplug if the battery charge a
          * significant amount since it has been plugged in.
          */
-        boolean shouldResetOnUnplugAfterSignificantCharge() {
+        public boolean shouldResetOnUnplugAfterSignificantCharge() {
             return (mFlags & RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG)
                     == RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG;
         }
 
-        long getPowerStatsThrottlePeriod(@BatteryConsumer.PowerComponent int powerComponent) {
-            return mPowerStatsThrottlePeriods.get(powerComponent,
-                    DEFAULT_POWER_STATS_COLLECTION_THROTTLE_PERIOD);
+        /**
+         * Returns  the minimum amount of time (in millis) to wait between passes
+         * of power stats collection for the specified power component.
+         */
+        public long getPowerStatsThrottlePeriod(String powerComponentName) {
+            return mPowerStatsThrottlePeriods.getOrDefault(powerComponentName,
+                    mDefaultPowerStatsThrottlePeriod);
         }
 
         /**
@@ -510,18 +521,19 @@
         public static class Builder {
             private boolean mResetOnUnplugHighBatteryLevel;
             private boolean mResetOnUnplugAfterSignificantCharge;
-            private SparseLongArray mPowerStatsThrottlePeriods;
+            public static final long DEFAULT_POWER_STATS_THROTTLE_PERIOD =
+                    TimeUnit.HOURS.toMillis(1);
+            public static final long DEFAULT_POWER_STATS_THROTTLE_PERIOD_CPU =
+                    TimeUnit.MINUTES.toMillis(1);
+            private long mDefaultPowerStatsThrottlePeriod = DEFAULT_POWER_STATS_THROTTLE_PERIOD;
+            private final Map<String, Long> mPowerStatsThrottlePeriods = new HashMap<>();
 
             public Builder() {
                 mResetOnUnplugHighBatteryLevel = true;
                 mResetOnUnplugAfterSignificantCharge = true;
-                mPowerStatsThrottlePeriods = new SparseLongArray();
-                setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_CPU,
-                        TimeUnit.MINUTES.toMillis(1));
-                setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
-                        TimeUnit.HOURS.toMillis(1));
-                setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_WIFI,
-                        TimeUnit.HOURS.toMillis(1));
+                setPowerStatsThrottlePeriodMillis(BatteryConsumer.powerComponentIdToString(
+                                BatteryConsumer.POWER_COMPONENT_CPU),
+                        DEFAULT_POWER_STATS_THROTTLE_PERIOD_CPU);
             }
 
             /**
@@ -553,9 +565,18 @@
              * Sets the minimum amount of time (in millis) to wait between passes
              * of power stats collection for the specified power component.
              */
-            public Builder setPowerStatsThrottlePeriodMillis(
-                    @BatteryConsumer.PowerComponent int powerComponent, long periodMs) {
-                mPowerStatsThrottlePeriods.put(powerComponent, periodMs);
+            public Builder setPowerStatsThrottlePeriodMillis(String powerComponentName,
+                    long periodMs) {
+                mPowerStatsThrottlePeriods.put(powerComponentName, periodMs);
+                return this;
+            }
+
+            /**
+             * Sets the minimum amount of time (in millis) to wait between passes
+             * of power stats collection for any components not configured explicitly.
+             */
+            public Builder setDefaultPowerStatsThrottlePeriodMillis(long periodMs) {
+                mDefaultPowerStatsThrottlePeriod = periodMs;
                 return this;
             }
         }
@@ -1586,8 +1607,7 @@
     protected final Constants mConstants;
 
     @VisibleForTesting
-    @GuardedBy("this")
-    protected BatteryStatsConfig mBatteryStatsConfig;
+    protected final BatteryStatsConfig mBatteryStatsConfig;
 
     @GuardedBy("this")
     private AlarmManager mAlarmManager = null;
@@ -1933,6 +1953,11 @@
         }
 
         @Override
+        public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+            return mBatteryStatsConfig.getPowerStatsThrottlePeriod(powerComponentName);
+        }
+
+        @Override
         public PowerStatsUidResolver getUidResolver() {
             return mPowerStatsUidResolver;
         }
@@ -11167,19 +11192,14 @@
                 mConstants.MAX_HISTORY_FILES, mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator,
                 mClock, mMonotonicClock, traceDelegate, eventLogger);
 
-        mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector,
-                mBatteryStatsConfig.getPowerStatsThrottlePeriod(
-                        BatteryConsumer.POWER_COMPONENT_CPU));
+        mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector);
         mCpuPowerStatsCollector.addConsumer(this::recordPowerStats);
 
         mMobileRadioPowerStatsCollector = new MobileRadioPowerStatsCollector(
-                mPowerStatsCollectorInjector, mBatteryStatsConfig.getPowerStatsThrottlePeriod(
-                BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO));
+                mPowerStatsCollectorInjector);
         mMobileRadioPowerStatsCollector.addConsumer(this::recordPowerStats);
 
-        mWifiPowerStatsCollector = new WifiPowerStatsCollector(
-                mPowerStatsCollectorInjector, mBatteryStatsConfig.getPowerStatsThrottlePeriod(
-                BatteryConsumer.POWER_COMPONENT_WIFI));
+        mWifiPowerStatsCollector = new WifiPowerStatsCollector(mPowerStatsCollectorInjector);
         mWifiPowerStatsCollector.addConsumer(this::recordPowerStats);
 
         mStartCount++;
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index f53a1b0..b5ef67b 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
@@ -59,6 +59,7 @@
         KernelCpuStatsReader getKernelCpuStatsReader();
         ConsumedEnergyRetriever getConsumedEnergyRetriever();
         IntSupplier getVoltageSupplier();
+        long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
 
         default int getDefaultCpuPowerBrackets() {
             return DEFAULT_CPU_POWER_BRACKETS;
@@ -94,9 +95,11 @@
     private int mLastVoltageMv;
     private long[] mLastConsumedEnergyUws;
 
-    public CpuPowerStatsCollector(Injector injector, long throttlePeriodMs) {
-        super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(),
-                injector.getClock());
+    CpuPowerStatsCollector(Injector injector) {
+        super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod(
+                        BatteryConsumer.powerComponentIdToString(
+                                BatteryConsumer.POWER_COMPONENT_CPU)),
+                injector.getUidResolver(), injector.getClock());
         mInjector = injector;
     }
 
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java
index 1bcb2c4..2a02bd0 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java
@@ -44,7 +44,7 @@
      * Declare that the stats array has a section capturing CPU time per scaling step
      */
     public void addDeviceSectionCpuTimeByScalingStep(int scalingStepCount) {
-        mDeviceCpuTimeByScalingStepPosition = addDeviceSection(scalingStepCount);
+        mDeviceCpuTimeByScalingStepPosition = addDeviceSection(scalingStepCount, "steps");
         mDeviceCpuTimeByScalingStepCount = scalingStepCount;
     }
 
@@ -72,7 +72,7 @@
      * Declare that the stats array has a section capturing CPU time in each cluster
      */
     public void addDeviceSectionCpuTimeByCluster(int clusterCount) {
-        mDeviceCpuTimeByClusterPosition = addDeviceSection(clusterCount);
+        mDeviceCpuTimeByClusterPosition = addDeviceSection(clusterCount, "clusters");
         mDeviceCpuTimeByClusterCount = clusterCount;
     }
 
@@ -102,7 +102,7 @@
     public void addUidSectionCpuTimeByPowerBracket(int[] scalingStepToPowerBracketMap) {
         mScalingStepToPowerBracketMap = scalingStepToPowerBracketMap;
         updatePowerBracketCount();
-        mUidPowerBracketsPosition = addUidSection(mUidPowerBracketCount);
+        mUidPowerBracketsPosition = addUidSection(mUidPowerBracketCount, "time");
     }
 
     private void updatePowerBracketCount() {
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java
index c34b8a8..57b7259 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java
@@ -16,7 +16,6 @@
 
 package com.android.server.power.stats;
 
-import android.os.BatteryStats;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -487,64 +486,4 @@
             stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray);
         }
     }
-
-    @Override
-    public String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
-        unpackPowerStatsDescriptor(descriptor);
-        StringBuilder sb = new StringBuilder();
-        int cpuScalingStepCount = mStatsLayout.getCpuScalingStepCount();
-        sb.append("steps: [");
-        for (int step = 0; step < cpuScalingStepCount; step++) {
-            if (step != 0) {
-                sb.append(", ");
-            }
-            sb.append(mStatsLayout.getTimeByScalingStep(stats, step));
-        }
-        int clusterCount = mStatsLayout.getCpuClusterCount();
-        sb.append("] clusters: [");
-        for (int cluster = 0; cluster < clusterCount; cluster++) {
-            if (cluster != 0) {
-                sb.append(", ");
-            }
-            sb.append(mStatsLayout.getTimeByCluster(stats, cluster));
-        }
-        sb.append("] uptime: ").append(mStatsLayout.getUsageDuration(stats));
-        int energyConsumerCount = mStatsLayout.getEnergyConsumerCount();
-        if (energyConsumerCount > 0) {
-            sb.append(" energy: [");
-            for (int i = 0; i < energyConsumerCount; i++) {
-                if (i != 0) {
-                    sb.append(", ");
-                }
-                sb.append(mStatsLayout.getConsumedEnergy(stats, i));
-            }
-            sb.append("]");
-        }
-        sb.append(" power: ").append(
-                BatteryStats.formatCharge(mStatsLayout.getDevicePowerEstimate(stats)));
-        return sb.toString();
-    }
-
-    @Override
-    String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
-        // Unsupported for this power component
-        return null;
-    }
-
-    @Override
-    public String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
-        unpackPowerStatsDescriptor(descriptor);
-        StringBuilder sb = new StringBuilder();
-        sb.append("[");
-        int powerBracketCount = mStatsLayout.getCpuPowerBracketCount();
-        for (int bracket = 0; bracket < powerBracketCount; bracket++) {
-            if (bracket != 0) {
-                sb.append(", ");
-            }
-            sb.append(mStatsLayout.getUidTimeByPowerBracket(stats, bracket));
-        }
-        sb.append("] power: ").append(
-                BatteryStats.formatCharge(mStatsLayout.getUidPowerEstimate(stats)));
-        return sb.toString();
-    }
 }
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
index 7bc6817..a96e01b 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
@@ -73,6 +73,7 @@
         Handler getHandler();
         Clock getClock();
         PowerStatsUidResolver getUidResolver();
+        long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
         PackageManager getPackageManager();
         ConsumedEnergyRetriever getConsumedEnergyRetriever();
         IntSupplier getVoltageSupplier();
@@ -104,8 +105,11 @@
     private long mLastCallDuration;
     private long mLastScanDuration;
 
-    public MobileRadioPowerStatsCollector(Injector injector, long throttlePeriodMs) {
-        super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(),
+    MobileRadioPowerStatsCollector(Injector injector) {
+        super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod(
+                        BatteryConsumer.powerComponentIdToString(
+                                BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)),
+                injector.getUidResolver(),
                 injector.getClock());
         mInjector = injector;
     }
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java
index 81d7c2f..07d78f8 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java
@@ -64,29 +64,30 @@
     }
 
     void addDeviceMobileActivity() {
-        mDeviceSleepTimePosition = addDeviceSection(1);
-        mDeviceIdleTimePosition = addDeviceSection(1);
-        mDeviceScanTimePosition = addDeviceSection(1);
-        mDeviceCallTimePosition = addDeviceSection(1);
+        mDeviceSleepTimePosition = addDeviceSection(1, "sleep");
+        mDeviceIdleTimePosition = addDeviceSection(1, "idle");
+        mDeviceScanTimePosition = addDeviceSection(1, "scan");
+        mDeviceCallTimePosition = addDeviceSection(1, "call", FLAG_OPTIONAL);
     }
 
     void addStateStats() {
-        mStateRxTimePosition = addStateSection(1);
+        mStateRxTimePosition = addStateSection(1, "rx");
         mStateTxTimesCount = ModemActivityInfo.getNumTxPowerLevels();
-        mStateTxTimesPosition = addStateSection(mStateTxTimesCount);
+        mStateTxTimesPosition = addStateSection(mStateTxTimesCount, "tx");
     }
 
     void addUidNetworkStats() {
-        mUidRxBytesPosition = addUidSection(1);
-        mUidTxBytesPosition = addUidSection(1);
-        mUidRxPacketsPosition = addUidSection(1);
-        mUidTxPacketsPosition = addUidSection(1);
+        mUidRxPacketsPosition = addUidSection(1, "rx-pkts");
+        mUidRxBytesPosition = addUidSection(1, "rx-B");
+        mUidTxPacketsPosition = addUidSection(1, "tx-pkts");
+        mUidTxBytesPosition = addUidSection(1, "tx-B");
     }
 
     @Override
     public void addDeviceSectionPowerEstimate() {
         super.addDeviceSectionPowerEstimate();
-        mDeviceCallPowerPosition = addDeviceSection(1);
+        // Printed as part of the PhoneCallPowerStatsProcessor
+        mDeviceCallPowerPosition = addDeviceSection(1, "call-power", FLAG_HIDDEN);
     }
 
     public void setDeviceSleepTime(long[] stats, long durationMillis) {
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java
index c97c64b..eebed2f 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java
@@ -398,37 +398,4 @@
             }
         }
     }
-
-    @Override
-    String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
-        unpackPowerStatsDescriptor(descriptor);
-        return "idle: " + mStatsLayout.getDeviceIdleTime(stats)
-                + " sleep: " + mStatsLayout.getDeviceSleepTime(stats)
-                + " scan: " + mStatsLayout.getDeviceScanTime(stats)
-                + " power: " + mStatsLayout.getDevicePowerEstimate(stats);
-    }
-
-    @Override
-    String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
-        unpackPowerStatsDescriptor(descriptor);
-        StringBuilder sb = new StringBuilder();
-        sb.append(descriptor.getStateLabel(key));
-        sb.append(" rx: ").append(mStatsLayout.getStateRxTime(stats));
-        sb.append(" tx: ");
-        for (int txLevel = 0; txLevel < ModemActivityInfo.getNumTxPowerLevels(); txLevel++) {
-            if (txLevel != 0) {
-                sb.append(", ");
-            }
-            sb.append(mStatsLayout.getStateTxTime(stats, txLevel));
-        }
-        return sb.toString();
-    }
-
-    @Override
-    String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
-        unpackPowerStatsDescriptor(descriptor);
-        return "rx: " + mStatsLayout.getUidRxPackets(stats)
-                + " tx: " + mStatsLayout.getUidTxPackets(stats)
-                + " power: " + mStatsLayout.getUidPowerEstimate(stats);
-    }
 }
diff --git a/services/core/java/com/android/server/power/stats/MultiStateStats.java b/services/core/java/com/android/server/power/stats/MultiStateStats.java
index 6c4a2b6..a822281 100644
--- a/services/core/java/com/android/server/power/stats/MultiStateStats.java
+++ b/services/core/java/com/android/server/power/stats/MultiStateStats.java
@@ -28,10 +28,8 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
-import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.function.Consumer;
-import java.util.function.Function;
 
 /**
  * Maintains multidimensional multi-state stats.  States could be something like on-battery (0,1),
@@ -287,6 +285,14 @@
         mCounter = new LongArrayMultiStateCounter(factory.mSerialStateCount, dimensionCount);
     }
 
+    public int getDimensionCount() {
+        return mFactory.mDimensionCount;
+    }
+
+    public States[] getStates() {
+        return mFactory.mStates;
+    }
+
     /**
      * Copies time-in-state and timestamps from the supplied prototype. Does not
      * copy accumulated counts.
@@ -343,11 +349,6 @@
         mTracking = false;
     }
 
-    @Override
-    public String toString() {
-        return mCounter.toString();
-    }
-
     /**
      * Stores contents in an XML doc.
      */
@@ -451,10 +452,9 @@
         return true;
     }
 
-    /**
-     * Prints the accumulated stats, one line of every combination of states that has data.
-     */
-    public void dump(PrintWriter pw, Function<long[], String> statsFormatter) {
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
         long[] values = new long[mCounter.getArrayLength()];
         States.forEachTrackedStateCombination(mFactory.mStates, states -> {
             mCounter.getCounts(values, mFactory.getSerialState(states));
@@ -469,18 +469,24 @@
                 return;
             }
 
-            StringBuilder sb = new StringBuilder();
+            if (!sb.isEmpty()) {
+                sb.append("\n");
+            }
+
+            sb.append("(");
+            boolean first = true;
             for (int i = 0; i < states.length; i++) {
                 if (mFactory.mStates[i].mTracked) {
-                    if (sb.length() != 0) {
+                    if (!first) {
                         sb.append(" ");
                     }
+                    first = false;
                     sb.append(mFactory.mStates[i].mLabels[states[i]]);
                 }
             }
-            sb.append(" ");
-            sb.append(statsFormatter.apply(values));
-            pw.println(sb);
+            sb.append(") ");
+            sb.append(Arrays.toString(values));
         });
+        return sb.toString();
     }
 }
diff --git a/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java
index 62b653f..5c545fd 100644
--- a/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java
@@ -76,21 +76,4 @@
                     stats.setDeviceStats(states, mTmpDeviceStats);
                 });
     }
-
-    @Override
-    String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
-        return "power: " + mStatsLayout.getDevicePowerEstimate(stats);
-    }
-
-    @Override
-    String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
-        // Unsupported for this power component
-        return null;
-    }
-
-    @Override
-    String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
-        // Unsupported for this power component
-        return null;
-    }
 }
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
index 6d58307..0528733 100644
--- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
@@ -436,36 +436,76 @@
 
     void dumpDevice(IndentingPrintWriter ipw) {
         if (mDeviceStats != null) {
-            ipw.println(mPowerStatsDescriptor.name);
-            ipw.increaseIndent();
-            mDeviceStats.dump(ipw, stats ->
-                    mConfig.getProcessor().deviceStatsToString(mPowerStatsDescriptor, stats));
-            ipw.decreaseIndent();
+            dumpMultiStateStats(ipw, mDeviceStats, mPowerStatsDescriptor.name, null,
+                    mPowerStatsDescriptor.getDeviceStatsFormatter());
         }
 
         if (mStateStats.size() != 0) {
             ipw.increaseIndent();
-            ipw.println(mPowerStatsDescriptor.name + " states");
-            ipw.increaseIndent();
+            String header = mPowerStatsDescriptor.name + " states";
+            PowerStats.PowerStatsFormatter formatter =
+                    mPowerStatsDescriptor.getStateStatsFormatter();
             for (int i = 0; i < mStateStats.size(); i++) {
                 int key = mStateStats.keyAt(i);
+                String stateLabel = mPowerStatsDescriptor.getStateLabel(key);
                 MultiStateStats stateStats = mStateStats.valueAt(i);
-                stateStats.dump(ipw, stats ->
-                        mConfig.getProcessor().stateStatsToString(mPowerStatsDescriptor, key,
-                                stats));
+                dumpMultiStateStats(ipw, stateStats, header, stateLabel, formatter);
             }
             ipw.decreaseIndent();
-            ipw.decreaseIndent();
         }
     }
 
     void dumpUid(IndentingPrintWriter ipw, int uid) {
         UidStats uidStats = mUidStats.get(uid);
         if (uidStats != null && uidStats.stats != null) {
-            ipw.println(mPowerStatsDescriptor.name);
-            ipw.increaseIndent();
-            uidStats.stats.dump(ipw, stats ->
-                    mConfig.getProcessor().uidStatsToString(mPowerStatsDescriptor, stats));
+            dumpMultiStateStats(ipw, uidStats.stats, mPowerStatsDescriptor.name, null,
+                    mPowerStatsDescriptor.getUidStatsFormatter());
+        }
+    }
+
+    private void dumpMultiStateStats(IndentingPrintWriter ipw, MultiStateStats stats,
+            String header, String additionalLabel,
+            PowerStats.PowerStatsFormatter statsFormatter) {
+        boolean[] firstLine = new boolean[]{true};
+        long[] values = new long[stats.getDimensionCount()];
+        MultiStateStats.States[] stateInfo = stats.getStates();
+        MultiStateStats.States.forEachTrackedStateCombination(stateInfo, states -> {
+            stats.getStats(values, states);
+            boolean nonZero = false;
+            for (long value : values) {
+                if (value != 0) {
+                    nonZero = true;
+                    break;
+                }
+            }
+            if (!nonZero) {
+                return;
+            }
+
+            if (firstLine[0]) {
+                ipw.println(header);
+                ipw.increaseIndent();
+            }
+            firstLine[0] = false;
+            StringBuilder sb = new StringBuilder();
+            sb.append("(");
+            boolean first = true;
+            for (int i = 0; i < states.length; i++) {
+                if (stateInfo[i].isTracked()) {
+                    if (!first) {
+                        sb.append(" ");
+                    }
+                    first = false;
+                    sb.append(stateInfo[i].getLabels()[states[i]]);
+                }
+            }
+            if (additionalLabel != null) {
+                sb.append(" ").append(additionalLabel);
+            }
+            sb.append(") ").append(statsFormatter.format(values));
+            ipw.println(sb);
+        });
+        if (!firstLine[0]) {
             ipw.decreaseIndent();
         }
     }
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsLayout.java b/services/core/java/com/android/server/power/stats/PowerStatsLayout.java
index aa96409..58efd94 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsLayout.java
@@ -33,13 +33,20 @@
     private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT = "dec";
     private static final String EXTRA_UID_POWER_POSITION = "up";
 
-    protected static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0;
     protected static final int UNSUPPORTED = -1;
+    protected static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0;
+    protected static final int FLAG_OPTIONAL = 1;
+    protected static final int FLAG_HIDDEN = 2;
+    protected static final int FLAG_FORMAT_AS_POWER = 4;
 
     private int mDeviceStatsArrayLength;
     private int mStateStatsArrayLength;
     private int mUidStatsArrayLength;
 
+    private StringBuilder mDeviceFormat = new StringBuilder();
+    private StringBuilder mStateFormat = new StringBuilder();
+    private StringBuilder mUidFormat = new StringBuilder();
+
     protected int mDeviceDurationPosition = UNSUPPORTED;
     private int mDeviceEnergyConsumerPosition;
     private int mDeviceEnergyConsumerCount;
@@ -65,29 +72,71 @@
         return mUidStatsArrayLength;
     }
 
-    protected int addDeviceSection(int length) {
+    /**
+     * @param label should not contain either spaces or colons
+     */
+    private void appendFormat(StringBuilder sb, int position, int length, String label,
+            int flags) {
+        if ((flags & FLAG_HIDDEN) != 0) {
+            return;
+        }
+
+        if (!sb.isEmpty()) {
+            sb.append(' ');
+        }
+
+        sb.append(label).append(':');
+        sb.append(position);
+        if (length != 1) {
+            sb.append('[').append(length).append(']');
+        }
+        if ((flags & FLAG_FORMAT_AS_POWER) != 0) {
+            sb.append('p');
+        }
+        if ((flags & FLAG_OPTIONAL) != 0) {
+            sb.append('?');
+        }
+    }
+
+    protected int addDeviceSection(int length, String label, int flags) {
         int position = mDeviceStatsArrayLength;
         mDeviceStatsArrayLength += length;
+        appendFormat(mDeviceFormat, position, length, label, flags);
         return position;
     }
 
-    protected int addStateSection(int length) {
+    protected int addDeviceSection(int length, String label) {
+        return addDeviceSection(length, label, 0);
+    }
+
+    protected int addStateSection(int length, String label, int flags) {
         int position = mStateStatsArrayLength;
         mStateStatsArrayLength += length;
+        appendFormat(mStateFormat, position, length, label, flags);
         return position;
     }
 
-    protected int addUidSection(int length) {
+    protected int addStateSection(int length, String label) {
+        return addStateSection(length, label, 0);
+    }
+
+
+    protected int addUidSection(int length, String label, int flags) {
         int position = mUidStatsArrayLength;
         mUidStatsArrayLength += length;
+        appendFormat(mUidFormat, position, length, label, flags);
         return position;
     }
 
+    protected int addUidSection(int length, String label) {
+        return addUidSection(length, label, 0);
+    }
+
     /**
      * Declare that the stats array has a section capturing usage duration
      */
     public void addDeviceSectionUsageDuration() {
-        mDeviceDurationPosition = addDeviceSection(1);
+        mDeviceDurationPosition = addDeviceSection(1, "usage", FLAG_OPTIONAL);
     }
 
     /**
@@ -109,7 +158,7 @@
      * PowerStatsService.
      */
     public void addDeviceSectionEnergyConsumers(int energyConsumerCount) {
-        mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount);
+        mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount, "energy");
         mDeviceEnergyConsumerCount = energyConsumerCount;
     }
 
@@ -137,7 +186,8 @@
      * Declare that the stats array has a section capturing a power estimate
      */
     public void addDeviceSectionPowerEstimate() {
-        mDevicePowerEstimatePosition = addDeviceSection(1);
+        mDevicePowerEstimatePosition = addDeviceSection(1, "power",
+                FLAG_FORMAT_AS_POWER | FLAG_OPTIONAL);
     }
 
     /**
@@ -159,7 +209,7 @@
      * Declare that the UID stats array has a section capturing a power estimate
      */
     public void addUidSectionPowerEstimate() {
-        mUidPowerEstimatePosition = addUidSection(1);
+        mUidPowerEstimatePosition = addUidSection(1, "power", FLAG_FORMAT_AS_POWER | FLAG_OPTIONAL);
     }
 
     /**
@@ -195,6 +245,9 @@
                 mDeviceEnergyConsumerCount);
         extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition);
         extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition);
+        extras.putString(PowerStats.Descriptor.EXTRA_DEVICE_STATS_FORMAT, mDeviceFormat.toString());
+        extras.putString(PowerStats.Descriptor.EXTRA_STATE_STATS_FORMAT, mStateFormat.toString());
+        extras.putString(PowerStats.Descriptor.EXTRA_UID_STATS_FORMAT, mUidFormat.toString());
     }
 
     /**
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
index 0d5c542..2fd0b9a 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
@@ -19,8 +19,6 @@
 import android.annotation.Nullable;
 import android.util.Log;
 
-import com.android.internal.os.PowerStats;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -47,12 +45,6 @@
 
     abstract void finish(PowerComponentAggregatedPowerStats stats);
 
-    abstract String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats);
-
-    abstract String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats);
-
-    abstract String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats);
-
     protected static class PowerEstimationPlan {
         private final AggregatedPowerStatsConfig.PowerComponent mConfig;
         public List<DeviceStateEstimation> deviceStateEstimations = new ArrayList<>();
diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
index 6321053..bd04199 100644
--- a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
@@ -56,6 +56,7 @@
         Handler getHandler();
         Clock getClock();
         PowerStatsUidResolver getUidResolver();
+        long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
         PackageManager getPackageManager();
         ConsumedEnergyRetriever getConsumedEnergyRetriever();
         IntSupplier getVoltageSupplier();
@@ -92,9 +93,11 @@
     private final SparseArray<WifiScanTimes> mLastScanTimes = new SparseArray<>();
     private long mLastWifiActiveDuration;
 
-    public WifiPowerStatsCollector(Injector injector, long throttlePeriodMs) {
-        super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(),
-                injector.getClock());
+    WifiPowerStatsCollector(Injector injector) {
+        super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod(
+                        BatteryConsumer.powerComponentIdToString(
+                                BatteryConsumer.POWER_COMPONENT_WIFI)),
+                injector.getUidResolver(), injector.getClock());
         mInjector = injector;
     }
 
diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java
index 0fa6ec6..e2e8226 100644
--- a/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java
@@ -65,28 +65,28 @@
         mPowerReportingSupported = powerReportingSupported;
         if (mPowerReportingSupported) {
             mDeviceActiveTimePosition = UNSPECIFIED;
-            mDeviceRxTimePosition = addDeviceSection(1);
-            mDeviceTxTimePosition = addDeviceSection(1);
-            mDeviceIdleTimePosition = addDeviceSection(1);
-            mDeviceScanTimePosition = addDeviceSection(1);
+            mDeviceRxTimePosition = addDeviceSection(1, "rx");
+            mDeviceTxTimePosition = addDeviceSection(1, "tx");
+            mDeviceIdleTimePosition = addDeviceSection(1, "idle");
+            mDeviceScanTimePosition = addDeviceSection(1, "scan");
         } else {
-            mDeviceActiveTimePosition = addDeviceSection(1);
+            mDeviceActiveTimePosition = addDeviceSection(1, "rx-tx");
             mDeviceRxTimePosition = UNSPECIFIED;
             mDeviceTxTimePosition = UNSPECIFIED;
             mDeviceIdleTimePosition = UNSPECIFIED;
             mDeviceScanTimePosition = UNSPECIFIED;
         }
-        mDeviceBasicScanTimePosition = addDeviceSection(1);
-        mDeviceBatchedScanTimePosition = addDeviceSection(1);
+        mDeviceBasicScanTimePosition = addDeviceSection(1, "basic-scan", FLAG_OPTIONAL);
+        mDeviceBatchedScanTimePosition = addDeviceSection(1, "batched-scan", FLAG_OPTIONAL);
     }
 
     void addUidNetworkStats() {
-        mUidRxBytesPosition = addUidSection(1);
-        mUidTxBytesPosition = addUidSection(1);
-        mUidRxPacketsPosition = addUidSection(1);
-        mUidTxPacketsPosition = addUidSection(1);
-        mUidScanTimePosition = addUidSection(1);
-        mUidBatchScanTimePosition = addUidSection(1);
+        mUidRxPacketsPosition = addUidSection(1, "rx-pkts");
+        mUidRxBytesPosition = addUidSection(1, "rx-B");
+        mUidTxPacketsPosition = addUidSection(1, "tx-pkts");
+        mUidTxBytesPosition = addUidSection(1, "tx-B");
+        mUidScanTimePosition = addUidSection(1, "scan", FLAG_OPTIONAL);
+        mUidBatchScanTimePosition = addUidSection(1, "batched-scan", FLAG_OPTIONAL);
     }
 
     public boolean isPowerReportingSupported() {
diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java
index 5e9cc40..a4a2e18 100644
--- a/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java
@@ -389,37 +389,4 @@
             }
         }
     }
-
-    @Override
-    String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
-        unpackPowerStatsDescriptor(descriptor);
-        if (mHasWifiPowerController) {
-            return "rx: " + mStatsLayout.getDeviceRxTime(stats)
-                    + " tx: " + mStatsLayout.getDeviceTxTime(stats)
-                    + " scan: " + mStatsLayout.getDeviceScanTime(stats)
-                    + " idle: " + mStatsLayout.getDeviceIdleTime(stats)
-                    + " power: " + mStatsLayout.getDevicePowerEstimate(stats);
-        } else {
-            return "active: " + mStatsLayout.getDeviceActiveTime(stats)
-                    + " scan: " + mStatsLayout.getDeviceBasicScanTime(stats)
-                    + " batched-scan: " + mStatsLayout.getDeviceBatchedScanTime(stats)
-                    + " power: " + mStatsLayout.getDevicePowerEstimate(stats);
-        }
-    }
-
-    @Override
-    String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
-        // Unsupported for this power component
-        return null;
-    }
-
-    @Override
-    String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
-        unpackPowerStatsDescriptor(descriptor);
-        return "rx: " + mStatsLayout.getUidRxPackets(stats)
-                + " tx: " + mStatsLayout.getUidTxPackets(stats)
-                + " scan: " + mStatsLayout.getUidScanTime(stats)
-                + " batched-scan: " + mStatsLayout.getUidBatchedScanTime(stats)
-                + " power: " + mStatsLayout.getUidPowerEstimate(stats);
-    }
 }
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 93f26ae..c85ceac 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -642,6 +642,8 @@
                         .getPackages()
                         .get(0)
                         .getVersionRolledBackFrom();
+        Slog.i(TAG, "Rolling back high impact rollback for package: "
+                + firstRollback.getPackageName());
         rollbackPackage(sortedHighImpactRollbacks.get(0), firstRollback, rollbackReason);
     }
 
diff --git a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
index 519c0ed..7fc0292 100644
--- a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
+++ b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
@@ -293,6 +293,8 @@
                 return "REASON_APP_NOT_RESPONDING";
             case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT:
                 return "REASON_NATIVE_CRASH_DURING_BOOT";
+            case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING:
+                return "REASON_BOOT_LOOP";
             default:
                 return "UNKNOWN";
         }
diff --git a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
index 2bf0b2c..55f85ea2 100644
--- a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
+++ b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
@@ -22,6 +22,8 @@
 import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE;
 import static android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -31,12 +33,20 @@
 import android.security.attestationverification.IAttestationVerificationManagerService;
 import android.security.attestationverification.IVerificationResult;
 import android.security.attestationverification.VerificationToken;
+import android.text.TextUtils;
 import android.util.ExceptionUtils;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
+import android.util.TimeUtils;
 
 import com.android.internal.infra.AndroidFuture;
+import com.android.internal.util.DumpUtils;
 import com.android.server.SystemService;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayDeque;
+
 /**
  * A {@link SystemService} which provides functionality related to verifying attestations of
  * (usually) remote computing environments.
@@ -46,11 +56,13 @@
 public class AttestationVerificationManagerService extends SystemService {
 
     private static final String TAG = "AVF";
+    private static final int DUMP_EVENT_LOG_SIZE = 10;
     private final AttestationVerificationPeerDeviceVerifier mPeerDeviceVerifier;
+    private final DumpLogger mDumpLogger = new DumpLogger();
 
     public AttestationVerificationManagerService(final Context context) throws Exception {
         super(context);
-        mPeerDeviceVerifier = new AttestationVerificationPeerDeviceVerifier(context);
+        mPeerDeviceVerifier = new AttestationVerificationPeerDeviceVerifier(context, mDumpLogger);
     }
 
     private final IBinder mService = new IAttestationVerificationManagerService.Stub() {
@@ -83,6 +95,28 @@
         private void enforceUsePermission() {
             getContext().enforceCallingOrSelfPermission(USE_ATTESTATION_VERIFICATION_SERVICE, null);
         }
+
+        @Override
+        protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
+                @Nullable String[] args) {
+            if (!android.security.Flags.dumpAttestationVerifications()) {
+                super.dump(fd, writer, args);
+                return;
+            }
+
+            if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, writer)) return;
+
+            final IndentingPrintWriter fout = new IndentingPrintWriter(writer, "    ");
+
+            fout.print("AttestationVerificationManagerService");
+            fout.println();
+            fout.increaseIndent();
+
+            fout.println("Event Log:");
+            fout.increaseIndent();
+            mDumpLogger.dumpTo(fout);
+            fout.decreaseIndent();
+        }
     };
 
     private void verifyAttestationForAllVerifiers(
@@ -119,4 +153,45 @@
         Slog.d(TAG, "Started");
         publishBinderService(Context.ATTESTATION_VERIFICATION_SERVICE, mService);
     }
+
+
+    static class DumpLogger {
+        private final ArrayDeque<DumpData> mData = new ArrayDeque<>(DUMP_EVENT_LOG_SIZE);
+        private int mEventsLogged = 0;
+
+        void logAttempt(DumpData data) {
+            synchronized (mData) {
+                if (mData.size() == DUMP_EVENT_LOG_SIZE) {
+                    mData.removeFirst();
+                }
+
+                mEventsLogged++;
+                data.mEventNumber = mEventsLogged;
+
+                data.mEventTimeMs = System.currentTimeMillis();
+
+                mData.add(data);
+            }
+        }
+
+        void dumpTo(IndentingPrintWriter writer) {
+            synchronized (mData) {
+                for (DumpData data : mData.reversed()) {
+                    writer.println(
+                            TextUtils.formatSimple("Verification #%d [%s]", data.mEventNumber,
+                                    TimeUtils.formatForLogging(data.mEventTimeMs)));
+                    writer.increaseIndent();
+                    data.dumpTo(writer);
+                    writer.decreaseIndent();
+                }
+            }
+        }
+    }
+
+    abstract static class DumpData {
+        protected int mEventNumber = -1;
+        protected long mEventTimeMs = -1;
+
+        abstract void dumpTo(IndentingPrintWriter writer);
+    }
 }
diff --git a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
index 72a402d..945a340 100644
--- a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
+++ b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
@@ -30,15 +30,19 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.os.Build;
 import android.os.Bundle;
+import android.security.attestationverification.AttestationVerificationManager;
 import android.security.attestationverification.AttestationVerificationManager.LocalBindingType;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.security.AttestationVerificationManagerService.DumpLogger;
 
 import org.json.JSONObject;
 
@@ -71,7 +75,9 @@
 
 /**
  * Verifies Android key attestation according to the
- * {@link android.security.attestationverification.AttestationVerificationManager#PROFILE_PEER_DEVICE PROFILE_PEER_DEVICE}
+ * {@link
+ * android.security.attestationverification.AttestationVerificationManager#PROFILE_PEER_DEVICE
+ * PROFILE_PEER_DEVICE}
  * profile.
  *
  * <p>
@@ -118,9 +124,12 @@
     private final LocalDate mTestLocalPatchDate;
     private final CertificateFactory mCertificateFactory;
     private final CertPathValidator mCertPathValidator;
+    private final DumpLogger mDumpLogger;
 
-    AttestationVerificationPeerDeviceVerifier(@NonNull Context context) throws Exception {
+    AttestationVerificationPeerDeviceVerifier(@NonNull Context context,
+            @NonNull DumpLogger dumpLogger) throws Exception {
         mContext = Objects.requireNonNull(context);
+        mDumpLogger = dumpLogger;
         mCertificateFactory = CertificateFactory.getInstance("X.509");
         mCertPathValidator = CertPathValidator.getInstance("PKIX");
         mTrustAnchors = getTrustAnchors();
@@ -132,9 +141,10 @@
     // Use ONLY for hermetic unit testing.
     @VisibleForTesting
     AttestationVerificationPeerDeviceVerifier(@NonNull Context context,
-            Set<TrustAnchor> trustAnchors, boolean revocationEnabled,
+            DumpLogger dumpLogger, Set<TrustAnchor> trustAnchors, boolean revocationEnabled,
             LocalDate systemDate, LocalDate localPatchDate) throws Exception {
         mContext = Objects.requireNonNull(context);
+        mDumpLogger = dumpLogger;
         mCertificateFactory = CertificateFactory.getInstance("X.509");
         mCertPathValidator = CertPathValidator.getInstance("PKIX");
         mTrustAnchors = trustAnchors;
@@ -153,63 +163,90 @@
      * bounded at the end by {@code -----END CERTIFICATE-----}.
      *
      * @param localBindingType Only {@code TYPE_PUBLIC_KEY} and {@code TYPE_CHALLENGE} supported.
-     * @param requirements Only {@code PARAM_PUBLIC_KEY} and {@code PARAM_CHALLENGE} supported.
-     * @param attestation Certificates should be DER encoded with leaf certificate appended first.
+     * @param requirements     Only {@code PARAM_PUBLIC_KEY} and {@code PARAM_CHALLENGE} supported.
+     * @param attestation      Certificates should be DER encoded with leaf certificate appended
+     *                         first.
      */
     int verifyAttestation(
             @LocalBindingType int localBindingType,
             @NonNull Bundle requirements,
             @NonNull byte[] attestation) {
+
+        MyDumpData dumpData = new MyDumpData();
+
+        int result =
+                verifyAttestationInternal(localBindingType, requirements, attestation, dumpData);
+        dumpData.mResult = result;
+        mDumpLogger.logAttempt(dumpData);
+        return result;
+    }
+
+    private int verifyAttestationInternal(
+            @LocalBindingType int localBindingType,
+            @NonNull Bundle requirements,
+            @NonNull byte[] attestation,
+            @NonNull MyDumpData dumpData) {
         if (mCertificateFactory == null) {
             debugVerboseLog("Unable to access CertificateFactory");
             return RESULT_FAILURE;
         }
+        dumpData.mCertificationFactoryAvailable = true;
 
         if (mCertPathValidator == null) {
             debugVerboseLog("Unable to access CertPathValidator");
             return RESULT_FAILURE;
         }
+        dumpData.mCertPathValidatorAvailable = true;
+
 
         // Check if the provided local binding type is supported and if the provided requirements
         // "match" the binding type.
         if (!validateAttestationParameters(localBindingType, requirements)) {
             return RESULT_FAILURE;
         }
+        dumpData.mAttestationParametersOk = true;
+
+        // To provide the most information in the dump logs, we track the failure state but keep
+        // verifying the rest of the attestation. For code safety, there are no transitions past
+        // here to set failed = false
+        boolean failed = false;
 
         try {
             // First: parse and validate the certificate chain.
             final List<X509Certificate> certificateChain = getCertificates(attestation);
             // (returns void, but throws CertificateException and other similar Exceptions)
             validateCertificateChain(certificateChain);
+            dumpData.mCertChainOk = true;
 
             final var leafCertificate = certificateChain.get(0);
             final var attestationExtension = fromCertificate(leafCertificate);
 
             // Second: verify if the attestation satisfies the "peer device" profile.
-            if (!checkAttestationForPeerDeviceProfile(attestationExtension)) {
-                return RESULT_FAILURE;
+            if (!checkAttestationForPeerDeviceProfile(attestationExtension, dumpData)) {
+                failed = true;
             }
 
             // Third: check if the attestation satisfies local binding requirements.
             if (!checkLocalBindingRequirements(
-                    leafCertificate, attestationExtension, localBindingType, requirements)) {
-                return RESULT_FAILURE;
+                    leafCertificate, attestationExtension, localBindingType, requirements,
+                    dumpData)) {
+                failed = true;
             }
-
-            return RESULT_SUCCESS;
         } catch (CertificateException | CertPathValidatorException
-                | InvalidAlgorithmParameterException | IOException e) {
+                 | InvalidAlgorithmParameterException | IOException e) {
             // Catch all non-RuntimeExpceptions (all of these are thrown by either getCertificates()
             // or validateCertificateChain() or
             // AndroidKeystoreAttestationVerificationAttributes.fromCertificate())
             debugVerboseLog("Unable to parse/validate Android Attestation certificate(s)", e);
-            return RESULT_FAILURE;
+            failed = true;
         } catch (RuntimeException e) {
             // Catch everyting else (RuntimeExpcetions), since we don't want to throw any exceptions
             // out of this class/method.
             debugVerboseLog("Unexpected error", e);
-            return RESULT_FAILURE;
+            failed = true;
         }
+
+        return failed ? RESULT_FAILURE : RESULT_SUCCESS;
     }
 
     @NonNull
@@ -255,7 +292,7 @@
 
     private void validateCertificateChain(List<X509Certificate> certificates)
             throws CertificateException, CertPathValidatorException,
-            InvalidAlgorithmParameterException  {
+            InvalidAlgorithmParameterException {
         if (certificates.size() < 2) {
             debugVerboseLog("Certificate chain less than 2 in size.");
             throw new CertificateException("Certificate chain less than 2 in size.");
@@ -277,7 +314,7 @@
     private Set<TrustAnchor> getTrustAnchors() throws CertPathValidatorException {
         Set<TrustAnchor> modifiableSet = new HashSet<>();
         try {
-            for (String certString: getTrustAnchorResources()) {
+            for (String certString : getTrustAnchorResources()) {
                 modifiableSet.add(
                         new TrustAnchor((X509Certificate) mCertificateFactory.generateCertificate(
                                 new ByteArrayInputStream(getCertificateBytes(certString))), null));
@@ -307,8 +344,9 @@
             @NonNull X509Certificate leafCertificate,
             @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes,
             @LocalBindingType int localBindingType,
-            @NonNull Bundle requirements) {
+            @NonNull Bundle requirements, MyDumpData dumpData) {
         // First: check non-optional (for the given local binding type) requirements.
+        dumpData.mBindingType = localBindingType;
         switch (localBindingType) {
             case TYPE_PUBLIC_KEY:
                 // Verify leaf public key matches provided public key.
@@ -336,9 +374,11 @@
                 throw new IllegalArgumentException("Unsupported local binding type "
                         + localBindingTypeToString(localBindingType));
         }
+        dumpData.mBindingOk = true;
 
         // Second: check specified optional requirements.
         if (requirements.containsKey(PARAM_OWNED_BY_SYSTEM)) {
+            dumpData.mSystemOwnershipChecked = true;
             if (requirements.getBoolean(PARAM_OWNED_BY_SYSTEM)) {
                 // Verify key is owned by the system.
                 final boolean ownedBySystem = checkOwnedBySystem(
@@ -347,6 +387,7 @@
                     debugVerboseLog("Certificate public key is not owned by the AndroidSystem.");
                     return false;
                 }
+                dumpData.mSystemOwned = true;
             } else {
                 throw new IllegalArgumentException("The value of the requirement key "
                         + PARAM_OWNED_BY_SYSTEM
@@ -359,73 +400,98 @@
     }
 
     private boolean checkAttestationForPeerDeviceProfile(
-            @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes) {
+            @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes,
+            MyDumpData dumpData) {
+        boolean result = true;
+
         // Checks for support of Keymaster 4.
         if (attestationAttributes.getAttestationVersion() < 3) {
             debugVerboseLog("Attestation version is not at least 3 (Keymaster 4).");
-            return false;
+            result = false;
+        } else {
+            dumpData.mAttestationVersionAtLeast3 = true;
         }
 
         // Checks for support of Keymaster 4.
         if (attestationAttributes.getKeymasterVersion() < 4) {
             debugVerboseLog("Keymaster version is not at least 4.");
-            return false;
+            result = false;
+        } else {
+            dumpData.mKeymasterVersionAtLeast4 = true;
         }
 
         // First two characters are Android OS version.
         if (attestationAttributes.getKeyOsVersion() < 100000) {
             debugVerboseLog("Android OS version is not 10+.");
-            return false;
+            result = false;
+        } else {
+            dumpData.mOsVersionAtLeast10 = true;
         }
 
         if (!attestationAttributes.isAttestationHardwareBacked()) {
             debugVerboseLog("Key is not HW backed.");
-            return false;
+            result = false;
+        } else {
+            dumpData.mKeyHwBacked = true;
         }
 
         if (!attestationAttributes.isKeymasterHardwareBacked()) {
             debugVerboseLog("Keymaster is not HW backed.");
-            return false;
+            result = false;
+        } else {
+            dumpData.mKeymasterHwBacked = true;
         }
 
         if (attestationAttributes.getVerifiedBootState() != VERIFIED) {
             debugVerboseLog("Boot state not Verified.");
-            return false;
+            result = false;
+        } else {
+            dumpData.mBootStateIsVerified = true;
         }
 
         try {
             if (!attestationAttributes.isVerifiedBootLocked()) {
                 debugVerboseLog("Verified boot state is not locked.");
-                return false;
+                result = false;
+            } else {
+                dumpData.mVerifiedBootStateLocked = true;
             }
         } catch (IllegalStateException e) {
             debugVerboseLog("VerifiedBootLocked is not set.", e);
-            return false;
+            result = false;
         }
 
         // Patch level integer YYYYMM is expected to be within 1 year of today.
         if (!isValidPatchLevel(attestationAttributes.getKeyOsPatchLevel())) {
             debugVerboseLog("OS patch level is not within valid range.");
-            return false;
+            result = false;
+        } else {
+            dumpData.mOsPatchLevelInRange = true;
         }
 
         // Patch level integer YYYYMMDD is expected to be within 1 year of today.
         if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) {
             debugVerboseLog("Boot patch level is not within valid range.");
-            return false;
+            result = false;
+        } else {
+            dumpData.mKeyBootPatchLevelInRange = true;
         }
 
         if (!isValidPatchLevel(attestationAttributes.getKeyVendorPatchLevel())) {
             debugVerboseLog("Vendor patch level is not within valid range.");
-            return false;
+            result = false;
+        } else {
+            dumpData.mKeyVendorPatchLevelInRange = true;
         }
 
         if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) {
             debugVerboseLog("Boot patch level is not within valid range.");
-            return false;
+            result = false;
+        } else {
+            dumpData.mKeyBootPatchLevelInRange = true;
         }
 
-        return true;
+        return result;
     }
 
     private boolean checkPublicKey(
@@ -609,4 +675,99 @@
             Slog.v(TAG, str);
         }
     }
+
+    /* Mutable data class for tracking dump data from verifications. */
+    private static class MyDumpData extends AttestationVerificationManagerService.DumpData {
+
+        // Top-Level Result
+        int mResult = -1;
+
+        // Configuration/Setup preconditions
+        boolean mCertificationFactoryAvailable = false;
+        boolean mCertPathValidatorAvailable = false;
+
+        // AttestationParameters (Valid Input Only)
+        boolean mAttestationParametersOk = false;
+
+        // Certificate Chain (Structure & Chaining Conditions)
+        boolean mCertChainOk = false;
+
+        // Binding
+        boolean mBindingOk = false;
+        int mBindingType = -1;
+
+        // System Ownership
+        boolean mSystemOwnershipChecked = false;
+        boolean mSystemOwned = false;
+
+        // Android Keystore attestation properties
+        boolean mOsVersionAtLeast10 = false;
+        boolean mKeyHwBacked = false;
+        boolean mAttestationVersionAtLeast3 = false;
+        boolean mKeymasterVersionAtLeast4 = false;
+        boolean mKeymasterHwBacked = false;
+        boolean mBootStateIsVerified = false;
+        boolean mVerifiedBootStateLocked = false;
+        boolean mOsPatchLevelInRange = false;
+        boolean mKeyBootPatchLevelInRange = false;
+        boolean mKeyVendorPatchLevelInRange = false;
+
+        @SuppressLint("WrongConstant")
+        @Override
+        public void dumpTo(IndentingPrintWriter writer) {
+            writer.println(
+                    "Result: " + AttestationVerificationManager.verificationResultCodeToString(
+                            mResult));
+            if (!mCertificationFactoryAvailable) {
+                writer.println("Certificate Factory Unavailable");
+                return;
+            }
+            if (!mCertPathValidatorAvailable) {
+                writer.println("Cert Path Validator Unavailable");
+                return;
+            }
+            if (!mAttestationParametersOk) {
+                writer.println("Attestation parameters set incorrectly.");
+                return;
+            }
+
+            writer.println("Certificate Chain Valid (inc. Trust Anchor): " + booleanToOkFail(
+                    mCertChainOk));
+            if (!mCertChainOk) {
+                return;
+            }
+
+            // Binding
+            writer.println("Local Binding: " + booleanToOkFail(mBindingOk));
+            writer.increaseIndent();
+            writer.println("Binding Type: " + mBindingType);
+            writer.decreaseIndent();
+
+            if (mSystemOwnershipChecked) {
+                writer.println("System Ownership: " + booleanToOkFail(mSystemOwned));
+            }
+
+            // Keystore Attestation params
+            writer.println("KeyStore Attestation Parameters");
+            writer.increaseIndent();
+            writer.println("OS Version >= 10: " + booleanToOkFail(mOsVersionAtLeast10));
+            writer.println("OS Patch Level in Range: " + booleanToOkFail(mOsPatchLevelInRange));
+            writer.println(
+                    "Attestation Version >= 3: " + booleanToOkFail(mAttestationVersionAtLeast3));
+            writer.println("Keymaster Version >= 4: " + booleanToOkFail(mKeymasterVersionAtLeast4));
+            writer.println("Keymaster HW-Backed: " + booleanToOkFail(mKeymasterHwBacked));
+            writer.println("Key is HW Backed: " + booleanToOkFail(mKeyHwBacked));
+            writer.println("Boot State is VERIFIED: " + booleanToOkFail(mBootStateIsVerified));
+            writer.println("Verified Boot is LOCKED: " + booleanToOkFail(mVerifiedBootStateLocked));
+            writer.println(
+                    "Key Boot Level in Range: " + booleanToOkFail(mKeyBootPatchLevelInRange));
+            writer.println("Key Vendor Patch Level in Range: " + booleanToOkFail(
+                    mKeyVendorPatchLevelInRange));
+            writer.decreaseIndent();
+        }
+
+        private String booleanToOkFail(boolean value) {
+            return value ? "OK" : "FAILURE";
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index e3e478d..c1b825b 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -49,10 +49,13 @@
 import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
 
 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
-import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON;
-import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU;
-import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE;
-import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__GESTURE_SHORTCUT_TYPE__TRIPLE_TAP;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__HARDWARE_SHORTCUT_TYPE__VOLUME_KEY;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__QS_SHORTCUT_TYPE__QUICK_SETTINGS;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_BUTTON;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_FLOATING_MENU;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_GESTURE;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__UNKNOWN_TYPE;
 import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__NOT_OPPORTUNISTIC;
 import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__OPPORTUNISTIC;
 import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__GEO;
@@ -61,7 +64,6 @@
 import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN;
 import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
 import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller;
-import static com.android.server.stats.Flags.statsPullNetworkStatsManagerInitOrderFix;
 import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
 import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
 import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines;
@@ -431,12 +433,6 @@
     public static final boolean ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER =
                 addMobileBytesTransferByProcStatePuller();
 
-    /**
-     * Whether or not to enable the mNetworkStatsManager initialization order fix
-     */
-    private static final boolean ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX =
-                statsPullNetworkStatsManagerInitOrderFix();
-
     // Puller locks
     private final Object mDataBytesTransferLock = new Object();
     private final Object mBluetoothBytesTransferLock = new Object();
@@ -799,7 +795,7 @@
                     case FrameworkStatsLog.KEYSTORE2_CRASH_STATS:
                         return pullKeystoreAtoms(atomTag, data);
                     case FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS:
-                        return pullAccessibilityShortcutStatsLocked(atomTag, data);
+                        return pullAccessibilityShortcutStatsLocked(data);
                     case FrameworkStatsLog.ACCESSIBILITY_FLOATING_MENU_STATS:
                         return pullAccessibilityFloatingMenuStatsLocked(atomTag, data);
                     case FrameworkStatsLog.MEDIA_CAPABILITIES:
@@ -840,9 +836,7 @@
                 registerEventListeners();
             });
         } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
-            if (ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) {
-                initNetworkStatsManager();
-            }
+            initNetworkStatsManager();
             BackgroundThread.getHandler().post(() -> {
                 // Network stats related pullers can only be initialized after service is ready.
                 initAndRegisterNetworkStatsPullers();
@@ -863,9 +857,6 @@
                 mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
         mStatsSubscriptionsListener = new StatsSubscriptionsListener(mSubscriptionManager);
         mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class);
-        if (!ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) {
-            initNetworkStatsManager();
-        }
 
         // Initialize DiskIO
         mStoragedUidIoStatsReader = new StoragedUidIoStatsReader();
@@ -1047,10 +1038,8 @@
      */
     @NonNull
     private NetworkStatsManager getNetworkStatsManager() {
-        if (ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) {
-            if (mNetworkStatsManager == null) {
-                throw new IllegalStateException("NetworkStatsManager is not ready");
-            }
+        if (mNetworkStatsManager == null) {
+            throw new IllegalStateException("NetworkStatsManager is not ready");
         }
         return mNetworkStatsManager;
     }
@@ -4774,7 +4763,10 @@
         }
     }
 
-    int pullAccessibilityShortcutStatsLocked(int atomTag, List<StatsEvent> pulledData) {
+    /**
+     * Pulls ACCESSIBILITY_SHORTCUT_STATS atom
+     */
+    int pullAccessibilityShortcutStatsLocked(List<StatsEvent> pulledData) {
         UserManager userManager = mContext.getSystemService(UserManager.class);
         if (userManager == null) {
             return StatsManager.PULL_SKIP;
@@ -4782,10 +4774,6 @@
         final long token = Binder.clearCallingIdentity();
         try {
             final ContentResolver resolver = mContext.getContentResolver();
-            final int hardware_shortcut_type =
-                    FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY;
-            final int triple_tap_shortcut =
-                    FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TRIPLE_TAP;
             for (UserInfo userInfo : userManager.getUsers()) {
                 final int userId = userInfo.getUserHandle().getIdentifier();
 
@@ -4803,15 +4791,22 @@
                     final int hardware_shortcut_service_num = countAccessibilityServices(
                             hardware_shortcut_list);
 
+                    final String qs_shortcut_list = Settings.Secure.getStringForUser(resolver,
+                            Settings.Secure.ACCESSIBILITY_QS_TARGETS, userId);
+                    final boolean qs_shortcut_enabled = !TextUtils.isEmpty(qs_shortcut_list);
+
                     // only allow magnification to use it for now
                     final int triple_tap_service_num = Settings.Secure.getIntForUser(resolver,
                             Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0, userId);
-
-                    pulledData.add(
-                            FrameworkStatsLog.buildStatsEvent(atomTag,
-                                    software_shortcut_type, software_shortcut_service_num,
-                                    hardware_shortcut_type, hardware_shortcut_service_num,
-                                    triple_tap_shortcut, triple_tap_service_num));
+                    pulledData.add(FrameworkStatsLog.buildStatsEvent(
+                            FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS,
+                            software_shortcut_type, software_shortcut_service_num,
+                            ACCESSIBILITY_SHORTCUT_STATS__HARDWARE_SHORTCUT_TYPE__VOLUME_KEY,
+                            hardware_shortcut_service_num,
+                            ACCESSIBILITY_SHORTCUT_STATS__GESTURE_SHORTCUT_TYPE__TRIPLE_TAP,
+                            triple_tap_service_num,
+                            ACCESSIBILITY_SHORTCUT_STATS__QS_SHORTCUT_TYPE__QUICK_SETTINGS,
+                            qs_shortcut_enabled));
                 }
             }
         } catch (RuntimeException e) {
@@ -5150,16 +5145,19 @@
                 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userId);
         final String hardware_shortcut_list = Settings.Secure.getStringForUser(resolver,
                 Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userId);
+        final String qs_shortcut_list = Settings.Secure.getStringForUser(resolver,
+                Settings.Secure.ACCESSIBILITY_QS_TARGETS, userId);
         final boolean hardware_shortcut_dialog_shown = Settings.Secure.getIntForUser(resolver,
                 Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId) == 1;
         final boolean software_shortcut_enabled = !TextUtils.isEmpty(software_shortcut_list);
         final boolean hardware_shortcut_enabled =
                 hardware_shortcut_dialog_shown && !TextUtils.isEmpty(hardware_shortcut_list);
+        final boolean qs_shortcut_enabled = !TextUtils.isEmpty(qs_shortcut_list);
         final boolean triple_tap_shortcut_enabled = Settings.Secure.getIntForUser(resolver,
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0, userId) == 1;
 
         return software_shortcut_enabled || hardware_shortcut_enabled
-                || triple_tap_shortcut_enabled;
+                || triple_tap_shortcut_enabled || qs_shortcut_enabled;
     }
 
     private boolean isAccessibilityFloatingMenuUser(Context context, @UserIdInt int userId) {
@@ -5176,13 +5174,13 @@
     private int convertToAccessibilityShortcutType(int shortcutType) {
         switch (shortcutType) {
             case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR:
-                return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON;
+                return ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_BUTTON;
             case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU:
-                return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU;
+                return ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_FLOATING_MENU;
             case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE:
-                return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE;
+                return ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_GESTURE;
             default:
-                return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
+                return ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__UNKNOWN_TYPE;
         }
     }
 
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
index c479c6d..6faa273 100644
--- a/services/core/java/com/android/server/stats/stats_flags.aconfig
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -8,11 +8,3 @@
     bug: "309512867"
     is_fixed_read_only: true
 }
-
-flag {
-    name: "stats_pull_network_stats_manager_init_order_fix"
-    namespace: "statsd"
-    description: "Fix the mNetworkStatsManager initialization order"
-    bug: "331989853"
-    is_fixed_read_only: true
-}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 2c67207..4264e91 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -20,9 +20,7 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS;
-import static android.app.StatusBarManager.DISABLE2_MASK;
 import static android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE;
-import static android.app.StatusBarManager.DISABLE_MASK;
 import static android.app.StatusBarManager.NAV_BAR_MODE_DEFAULT;
 import static android.app.StatusBarManager.NAV_BAR_MODE_KIDS;
 import static android.app.StatusBarManager.NavBarMode;
@@ -222,9 +220,8 @@
         int what1;
         int what2;
         IBinder token;
-        private String mReason;
 
-        DisableRecord(int userId, IBinder token) {
+        public DisableRecord(int userId, IBinder token) {
             this.userId = userId;
             this.token = token;
             try {
@@ -237,12 +234,12 @@
         @Override
         public void binderDied() {
             Slog.i(TAG, "binder died for pkg=" + pkg);
-            StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
-            disableForUser(info, token, pkg, userId, "Binder Died");
+            disableForUser(0, token, pkg, userId);
+            disable2ForUser(0, token, pkg, userId);
             token.unlinkToDeath(this, 0);
         }
 
-        public void setFlags(int what, int which, String pkg, String reason) {
+        public void setFlags(int what, int which, String pkg) {
             switch (which) {
                 case 1:
                     what1 = what;
@@ -256,7 +253,6 @@
                     break;
             }
             this.pkg = pkg;
-            this.mReason = reason;
         }
 
         public int getFlags(int which) {
@@ -275,8 +271,8 @@
 
         @Override
         public String toString() {
-            return String.format("userId=%d what1=0x%08X what2=0x%08X pkg=%s token=%s reason=%s",
-                    userId, what1, what2, pkg, token, mReason);
+            return String.format("userId=%d what1=0x%08X what2=0x%08X pkg=%s token=%s",
+                    userId, what1, what2, pkg, token);
         }
     }
 
@@ -954,7 +950,7 @@
 
         if (mBar != null) {
             try {
-                mBar.togglePanel();
+                mBar.toggleNotificationsPanel();
             } catch (RemoteException ex) {
             }
         }
@@ -1184,59 +1180,57 @@
         return mTracingEnabled;
     }
 
-    /**
-     * @deprecated
-     * Disable some features in the status bar.
-     *
-     * This method is deprecated and callers should use
-     * {@link #disableForUser(StatusBarManager.DisableInfo, IBinder, String, int, String)}
-     *
-     * @hide
-     */
-    @Deprecated
+    // TODO(b/117478341): make it aware of multi-display if needed.
     @Override
     public void disable(int what, IBinder token, String pkg) {
-        StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(what & DISABLE_MASK,
-                what & DISABLE2_MASK);
-        disableForUser(info, token, pkg, mCurrentUserId, null);
-    }
-
-    /**
-     * @deprecated
-     * Disable some features in the status bar.
-     *
-     * This method is deprecated and callers should use
-     * {@link #disableForUser(StatusBarManager.DisableInfo, IBinder, String, int, String)}
-     *
-     * @hide
-     */
-    @Deprecated
-    @Override
-    public void disable2(int what, IBinder token, String pkg) {
-        StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(what & DISABLE_MASK,
-                what & DISABLE2_MASK);
-        disableForUser(info, token, pkg, mCurrentUserId, null);
+        disableForUser(what, token, pkg, mCurrentUserId);
     }
 
     // TODO(b/117478341): make it aware of multi-display if needed.
     @Override
-    public void disableForUser(StatusBarManager.DisableInfo disableInfo, IBinder token, String pkg,
-            int userId, String reason) {
+    public void disableForUser(int what, IBinder token, String pkg, int userId) {
         enforceStatusBar();
+
         synchronized (mLock) {
-            Pair<Integer, Integer> flags = disableInfo.toFlags();
-            disableLocked(DEFAULT_DISPLAY, userId, flags.first, token, pkg, 1, reason);
-            disableLocked(DEFAULT_DISPLAY, userId, flags.second, token, pkg, 2, reason);
+            disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 1);
+        }
+    }
+
+    // TODO(b/117478341): make it aware of multi-display if needed.
+    /**
+     * Disable additional status bar features. Pass the bitwise-or of the DISABLE2_* flags.
+     * To re-enable everything, pass {@link #DISABLE2_NONE}.
+     *
+     * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags.
+     */
+    @Override
+    public void disable2(int what, IBinder token, String pkg) {
+        disable2ForUser(what, token, pkg, mCurrentUserId);
+    }
+
+    // TODO(b/117478341): make it aware of multi-display if needed.
+    /**
+     * Disable additional status bar features for a given user. Pass the bitwise-or of the
+     * DISABLE2_* flags. To re-enable everything, pass {@link #DISABLE_NONE}.
+     *
+     * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags.
+     */
+    @Override
+    public void disable2ForUser(int what, IBinder token, String pkg, int userId) {
+        enforceStatusBar();
+
+        synchronized (mLock) {
+            disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 2);
         }
     }
 
     private void disableLocked(int displayId, int userId, int what, IBinder token, String pkg,
-            int whichFlag, String reason) {
+            int whichFlag) {
         // It's important that the the callback and the call to mBar get done
         // in the same order when multiple threads are calling this function
         // so they are paired correctly.  The messages on the handler will be
         // handled in the order they were enqueued, but will be outside the lock.
-        manageDisableListLocked(userId, what, token, pkg, whichFlag, reason);
+        manageDisableListLocked(userId, what, token, pkg, whichFlag);
 
         // Ensure state for the current user is applied, even if passed a non-current user.
         final int net1 = gatherDisableActionsLocked(mCurrentUserId, 1);
@@ -1385,7 +1379,7 @@
         // also allows calls from window manager which is in this process.
         enforceStatusBarService();
 
-        final int unknownFlags = flags & ~DISABLE_MASK;
+        final int unknownFlags = flags & ~StatusBarManager.DISABLE_MASK;
         if (unknownFlags != 0) {
             Slog.e(TAG, "Unknown disable flags: 0x" + Integer.toHexString(unknownFlags),
                     new RuntimeException());
@@ -1394,8 +1388,7 @@
         if (SPEW) Slog.d(TAG, "setDisableFlags(0x" + Integer.toHexString(flags) + ")");
 
         synchronized (mLock) {
-            disableLocked(displayId, mCurrentUserId, flags, mSysUiVisToken, cause, 1,
-                    "setDisableFlags");
+            disableLocked(displayId, mCurrentUserId, flags, mSysUiVisToken, cause, 1);
         }
     }
 
@@ -2450,8 +2443,7 @@
     // ================================================================================
 
     // lock on mDisableRecords
-    void manageDisableListLocked(int userId, int what, IBinder token, String pkg, int which,
-            String reason) {
+    void manageDisableListLocked(int userId, int what, IBinder token, String pkg, int which) {
         if (SPEW) {
             Slog.d(TAG, "manageDisableList userId=" + userId
                     + " what=0x" + Integer.toHexString(what) + " pkg=" + pkg);
@@ -2473,7 +2465,7 @@
 
         // Update existing record
         if (record != null) {
-            record.setFlags(what, which, pkg, reason);
+            record.setFlags(what, which, pkg);
             if (record.isEmpty()) {
                 mDisableRecords.remove(i);
                 record.token.unlinkToDeath(record, 0);
@@ -2483,7 +2475,7 @@
 
         // Record doesn't exist, so we create a new one
         record = new DisableRecord(userId, token);
-        record.setFlags(what, which, pkg, reason);
+        record.setFlags(what, which, pkg);
         mDisableRecords.add(record);
     }
 
diff --git a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
index adb55b4..d6bf02f 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
@@ -16,6 +16,8 @@
 
 import static android.app.StatusBarManager.DEFAULT_SETUP_DISABLE2_FLAGS;
 import static android.app.StatusBarManager.DEFAULT_SETUP_DISABLE_FLAGS;
+import static android.app.StatusBarManager.DISABLE2_NONE;
+import static android.app.StatusBarManager.DISABLE_NONE;
 
 import android.app.StatusBarManager.DisableInfo;
 import android.content.ComponentName;
@@ -25,6 +27,7 @@
 import android.os.RemoteException;
 import android.os.ShellCommand;
 import android.service.quicksettings.TileService;
+import android.util.Pair;
 
 import java.io.PrintWriter;
 
@@ -141,17 +144,25 @@
         String arg = getNextArgRequired();
         String pkg = mContext.getPackageName();
         boolean disable = Boolean.parseBoolean(arg);
-        int userId = Binder.getCallingUserHandle().getIdentifier();
-        DisableInfo info = disable ? new DisableInfo(DEFAULT_SETUP_DISABLE_FLAGS,
-                DEFAULT_SETUP_DISABLE2_FLAGS) : new DisableInfo();
-        mInterface.disableForUser(info, sToken, pkg, userId, "runDisableForSetup");
+
+        if (disable) {
+            mInterface.disable(DEFAULT_SETUP_DISABLE_FLAGS, sToken, pkg);
+            mInterface.disable2(DEFAULT_SETUP_DISABLE2_FLAGS, sToken, pkg);
+        } else {
+            mInterface.disable(DISABLE_NONE, sToken, pkg);
+            mInterface.disable2(DISABLE2_NONE, sToken, pkg);
+        }
+
         return 0;
     }
 
     private int runSendDisableFlag() {
         String pkg = mContext.getPackageName();
-        int userId = Binder.getCallingUserHandle().getIdentifier();
+        int disable1 = DISABLE_NONE;
+        int disable2 = DISABLE2_NONE;
+
         DisableInfo info = new DisableInfo();
+
         String arg = getNextArg();
         while (arg != null) {
             switch (arg) {
@@ -159,7 +170,7 @@
                     info.setSearchDisabled(true);
                     break;
                 case "home":
-                    info.setNavigationHomeDisabled(true);
+                    info.setNagivationHomeDisabled(true);
                     break;
                 case "recents":
                     info.setRecentsDisabled(true);
@@ -186,7 +197,10 @@
             arg = getNextArg();
         }
 
-        mInterface.disableForUser(info, sToken, pkg, userId, "Shell Commands");
+        Pair<Integer, Integer> flagPair = info.toFlags();
+
+        mInterface.disable(flagPair.first, sToken, pkg);
+        mInterface.disable2(flagPair.second, sToken, pkg);
 
         return 0;
     }
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
index 3619253..47425322 100644
--- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -115,6 +115,10 @@
     // validation failure.
     private static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT = 12;
 
+    /** Carriers can disable the detector by setting the threshold to -1 */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR = -1;
+
     private static final int POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT = 20;
 
     // By default, there's no maximum limit enforced
@@ -271,7 +275,10 @@
         // When multiple parallel inbound transforms are created, NetworkMetricMonitor will be
         // enabled on the last one as a sample
         mInboundTransform = inboundTransform;
-        start();
+
+        if (!Flags.allowDisableIpsecLossDetector() || canStart()) {
+            start();
+        }
     }
 
     @Override
@@ -284,6 +291,14 @@
             mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
             mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig);
         }
+
+        if (Flags.allowDisableIpsecLossDetector() && canStart() != isStarted()) {
+            if (canStart()) {
+                start();
+            } else {
+                stop();
+            }
+        }
     }
 
     @Override
@@ -298,6 +313,12 @@
         mHandler.postDelayed(new PollIpSecStateRunnable(), mCancellationToken, 0L);
     }
 
+    private boolean canStart() {
+        return mInboundTransform != null
+                && mPacketLossRatePercentThreshold
+                        != IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR;
+    }
+
     @Override
     protected void start() {
         super.start();
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index d20b3b2..f8eb789 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -3646,7 +3646,8 @@
             }
             // System wallpaper does not support multiple displays, attach this display to
             // the fallback wallpaper.
-            if (mFallbackWallpaper != null) {
+            if (mFallbackWallpaper != null && mFallbackWallpaper
+                        .connection != null) {
                 final DisplayConnector connector = mFallbackWallpaper
                         .connection.getDisplayConnectorOrCreate(displayId);
                 if (connector == null) return;
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index f6afc52..3393d3e 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -150,7 +150,11 @@
     @Override
     public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
             DisplayInfo[] displayInfos) {
-        mHandler.post(() -> onWindowInfosChangedInternal(windowHandles, displayInfos));
+        if (com.android.server.accessibility.Flags.removeOnWindowInfosChangedHandler()) {
+            onWindowInfosChangedInternal(windowHandles, displayInfos);
+        } else {
+            mHandler.post(() -> onWindowInfosChangedInternal(windowHandles, displayInfos));
+        }
     }
 
     private void onWindowInfosChangedInternal(InputWindowHandle[] windowHandles,
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index c5683f3..c9395da 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -957,6 +957,7 @@
     public boolean enterPictureInPictureMode(IBinder token, final PictureInPictureParams params) {
         final long origId = Binder.clearCallingIdentity();
         try {
+            ensureSetPipAspectRatioQuotaTracker();
             synchronized (mGlobalLock) {
                 final ActivityRecord r = ensureValidPictureInPictureActivityParams(
                         "enterPictureInPictureMode", token, params);
@@ -971,6 +972,7 @@
     public void setPictureInPictureParams(IBinder token, final PictureInPictureParams params) {
         final long origId = Binder.clearCallingIdentity();
         try {
+            ensureSetPipAspectRatioQuotaTracker();
             synchronized (mGlobalLock) {
                 final ActivityRecord r = ensureValidPictureInPictureActivityParams(
                         "setPictureInPictureParams", token, params);
@@ -1023,6 +1025,19 @@
     }
 
     /**
+     * Initialize the {@link #mSetPipAspectRatioQuotaTracker} if applicable, which should happen
+     * out of {@link #mGlobalLock} to avoid deadlock (AM lock is used in QuotaTrack ctor).
+     */
+    private void ensureSetPipAspectRatioQuotaTracker() {
+        if (mSetPipAspectRatioQuotaTracker == null) {
+            mSetPipAspectRatioQuotaTracker = new CountQuotaTracker(mContext,
+                    Categorizer.SINGLE_CATEGORIZER);
+            mSetPipAspectRatioQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY,
+                    SET_PIP_ASPECT_RATIO_LIMIT, SET_PIP_ASPECT_RATIO_TIME_WINDOW_MS);
+        }
+    }
+
+    /**
      * Checks the state of the system and the activity associated with the given {@param token} to
      * verify that picture-in-picture is supported for that activity.
      *
@@ -1049,12 +1064,6 @@
         // Rate limit how frequent an app can request aspect ratio change via
         // Activity#setPictureInPictureParams
         final int userId = UserHandle.getCallingUserId();
-        if (mSetPipAspectRatioQuotaTracker == null) {
-            mSetPipAspectRatioQuotaTracker = new CountQuotaTracker(mContext,
-                    Categorizer.SINGLE_CATEGORIZER);
-            mSetPipAspectRatioQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY,
-                    SET_PIP_ASPECT_RATIO_LIMIT, SET_PIP_ASPECT_RATIO_TIME_WINDOW_MS);
-        }
         if (r.pictureInPictureArgs.hasSetAspectRatio()
                 && params.hasSetAspectRatio()
                 && !r.pictureInPictureArgs.getAspectRatio().equals(
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e814f17..d053bbb 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -77,7 +77,6 @@
 import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
 import static android.content.pm.ActivityInfo.FLAG_STATE_NOT_NEEDED;
 import static android.content.pm.ActivityInfo.FLAG_TURN_SCREEN_ON;
-import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION;
 import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED;
 import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
@@ -87,6 +86,7 @@
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
+import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION;
 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN;
 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE;
 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM;
@@ -121,6 +121,7 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 import static android.view.WindowManager.ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15;
+import static android.view.WindowManager.ENABLE_ACTIVITY_EMBEDDING_FOR_ANDROID_15;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -128,7 +129,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED;
 import static android.view.WindowManager.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING;
-import static android.view.WindowManager.ENABLE_ACTIVITY_EMBEDDING_FOR_ANDROID_15;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
 import static android.view.WindowManager.TRANSIT_OLD_UNSET;
@@ -339,7 +339,6 @@
 import android.service.dreams.DreamActivity;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.ArraySet;
-import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.MergedConfiguration;
@@ -683,6 +682,12 @@
     // it references to gets removed. This should also be cleared when we move out of pip.
     private Task mLastParentBeforePip;
 
+    // The token of the previous TaskFragment parent of this embedded ActivityRecord when it is
+    // reparented to a new Task due to picture-in-picture.
+    // Note that the TaskFragment may be finished and no longer attached in WM hierarchy.
+    @Nullable
+    private IBinder mLastEmbeddedParentTfTokenBeforePip;
+
     // Only set if this instance is a launch-into-pip Activity, points to the
     // host Activity the launch-into-pip Activity is originated from.
     private ActivityRecord mLaunchIntoPipHostActivity;
@@ -1807,6 +1812,11 @@
         mLastTaskFragmentOrganizerBeforePip = organizedTf != null
                 ? organizedTf.getTaskFragmentOrganizer()
                 : null;
+        if (organizedTf != null
+                // Not necessary for content pip.
+                && launchIntoPipHostActivity == null) {
+            mLastEmbeddedParentTfTokenBeforePip = organizedTf.getFragmentToken();
+        }
     }
 
     void clearLastParentBeforePip() {
@@ -1816,12 +1826,17 @@
         }
         mLaunchIntoPipHostActivity = null;
         mLastTaskFragmentOrganizerBeforePip = null;
+        mLastEmbeddedParentTfTokenBeforePip = null;
     }
 
     @Nullable Task getLastParentBeforePip() {
         return mLastParentBeforePip;
     }
 
+    @Nullable IBinder getLastEmbeddedParentTfTokenBeforePip() {
+        return mLastEmbeddedParentTfTokenBeforePip;
+    }
+
     @Nullable ActivityRecord getLaunchIntoPipHostActivity() {
         return mLaunchIntoPipHostActivity;
     }
@@ -2123,14 +2138,14 @@
         if (mWmService.mFlags.mInsetsDecoupledConfiguration) {
             // When the stable configuration is the default behavior, override for the legacy apps
             // without forward override flag.
-            mResolveConfigHint.mUseOverrideInsetsForStableBounds =
+            mResolveConfigHint.mUseOverrideInsetsForConfig =
                     !info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
                             && !info.isChangeEnabled(
                                     OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION);
         } else {
             // When the stable configuration is not the default behavior, forward overriding the
             // listed apps.
-            mResolveConfigHint.mUseOverrideInsetsForStableBounds =
+            mResolveConfigHint.mUseOverrideInsetsForConfig =
                     info.isChangeEnabled(OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION);
         }
 
@@ -3272,8 +3287,12 @@
         mOccludesParent = occludesParent;
         setMainWindowOpaque(occludesParent);
 
-        if (changed && task != null && !occludesParent) {
-            getRootTask().convertActivityToTranslucent(this);
+        if (changed && task != null) {
+            if (!occludesParent) {
+                getRootTask().convertActivityToTranslucent(this);
+            } else {
+                getRootTask().convertActivityFromTranslucent(this);
+            }
         }
         // Always ensure visibility if this activity doesn't occlude parent, so the
         // {@link #returningOptions} of the activity under this one can be applied in
@@ -4266,6 +4285,12 @@
         getTaskFragment().cleanUpActivityReferences(this);
         clearLastParentBeforePip();
 
+        // Abort and reset state if the scence transition is playing.
+        final Task rootTask = getRootTask();
+        if (rootTask != null) {
+            rootTask.abortTranslucentActivityWaiting(this);
+        }
+
         // Clean up the splash screen if it was still displayed.
         cleanUpSplashScreen();
 
@@ -5672,6 +5697,8 @@
                 } else if (mTransitionController.inFinishingTransition(this)) {
                     mTransitionChangeFlags |= FLAGS_IS_OCCLUDED_NO_ANIMATION;
                 }
+            } else {
+                mTransitionChangeFlags &= ~FLAG_IS_OCCLUDED;
             }
             return;
         }
@@ -6519,8 +6546,8 @@
             // and the token could be null.
             return;
         }
-        if (r.mDisplayContent.mDisplayRotationCompatPolicy != null) {
-            r.mDisplayContent.mDisplayRotationCompatPolicy.onActivityRefreshed(r);
+        if (r.mDisplayContent.mActivityRefresher != null) {
+            r.mDisplayContent.mActivityRefresher.onActivityRefreshed(r);
         }
     }
 
@@ -8480,7 +8507,7 @@
         mCompatDisplayInsets =
                 new CompatDisplayInsets(
                         mDisplayContent, this, letterboxedContainerBounds,
-                        mResolveConfigHint.mUseOverrideInsetsForStableBounds);
+                        mResolveConfigHint.mUseOverrideInsetsForConfig);
     }
 
     private void clearSizeCompatModeAttributes() {
@@ -8560,8 +8587,6 @@
         final int parentWindowingMode =
                 newParentConfiguration.windowConfiguration.getWindowingMode();
 
-        applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig);
-
         // Bubble activities should always fill their parent and should not be letterboxed.
         final boolean isFixedOrientationLetterboxAllowed = !getLaunchedFromBubble()
                 && (parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
@@ -8661,6 +8686,8 @@
             resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
         }
 
+        applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig);
+
         logAppCompatState();
     }
 
@@ -8679,14 +8706,13 @@
         if (mDisplayContent == null) {
             return;
         }
-        final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
         int rotation = newParentConfiguration.windowConfiguration.getRotation();
         if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) {
             rotation = mDisplayContent.getRotation();
         }
-        if (!mResolveConfigHint.mUseOverrideInsetsForStableBounds
-                || getCompatDisplayInsets() != null || isFloating(parentWindowingMode)
-                || rotation == ROTATION_UNDEFINED) {
+        if (!mResolveConfigHint.mUseOverrideInsetsForConfig
+                || getCompatDisplayInsets() != null || shouldCreateCompatDisplayInsets()
+                || isFloating(parentWindowingMode) || rotation == ROTATION_UNDEFINED) {
             // If the insets configuration decoupled logic is not enabled for the app, or the app
             // already has a compat override, or the context doesn't contain enough info to
             // calculate the override, skip the override.
@@ -8703,53 +8729,7 @@
         }
 
         // Override starts here.
-        final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
-        final int dw = rotated ? mDisplayContent.mBaseDisplayHeight
-                : mDisplayContent.mBaseDisplayWidth;
-        final int dh = rotated ? mDisplayContent.mBaseDisplayWidth
-                : mDisplayContent.mBaseDisplayHeight;
-        final  Rect nonDecorInsets = mDisplayContent.getDisplayPolicy()
-                .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets;
-        // This should be the only place override the configuration for ActivityRecord. Override
-        // the value if not calculated yet.
-        Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
-        if (outAppBounds == null || outAppBounds.isEmpty()) {
-            inOutConfig.windowConfiguration.setAppBounds(parentBounds);
-            outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
-            outAppBounds.inset(nonDecorInsets);
-        }
-        float density = inOutConfig.densityDpi;
-        if (density == Configuration.DENSITY_DPI_UNDEFINED) {
-            density = newParentConfiguration.densityDpi;
-        }
-        density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
-        if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
-            final int overrideScreenWidthDp = (int) (outAppBounds.width() / density + 0.5f);
-            inOutConfig.screenWidthDp = overrideScreenWidthDp;
-        }
-        if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
-            final int overrideScreenHeightDp = (int) (outAppBounds.height() / density + 0.5f);
-            inOutConfig.screenHeightDp = overrideScreenHeightDp;
-        }
-        if (inOutConfig.smallestScreenWidthDp
-                == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
-                && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
-            // For the case of PIP transition and multi-window environment, the
-            // smallestScreenWidthDp is handled already. Override only if the app is in
-            // fullscreen.
-            final DisplayInfo info = new DisplayInfo(mDisplayContent.getDisplayInfo());
-            mDisplayContent.computeSizeRanges(info, rotated, dw, dh,
-                    mDisplayContent.getDisplayMetrics().density,
-                    inOutConfig, true /* overrideConfig */);
-        }
-
-        // It's possible that screen size will be considered in different orientation with or
-        // without considering the system bar insets. Override orientation as well.
-        if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
-            inOutConfig.orientation =
-                    (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
-                            ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
-        }
+        computeConfigByResolveHint(inOutConfig, newParentConfiguration);
     }
 
     private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig,
@@ -9005,7 +8985,7 @@
         if (mDisplayContent == null) {
             return true;
         }
-        if (!mResolveConfigHint.mUseOverrideInsetsForStableBounds) {
+        if (!mResolveConfigHint.mUseOverrideInsetsForConfig) {
             // No insets should be considered any more.
             return true;
         }
@@ -9024,7 +9004,7 @@
         final Task task = getTask();
         task.calculateInsetFrames(outNonDecorBounds /* outNonDecorBounds */,
                 outStableBounds /* outStableBounds */, parentBounds /* bounds */, di,
-                mResolveConfigHint.mUseOverrideInsetsForStableBounds);
+                mResolveConfigHint.mUseOverrideInsetsForConfig);
         final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width()
                 ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
         // If orientation does not match the orientation with insets applied, then a
@@ -9081,7 +9061,7 @@
                 getResolvedOverrideConfiguration().windowConfiguration.getBounds();
         final int stableBoundsOrientation = stableBounds.width() > stableBounds.height()
                 ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
-        final int parentOrientation = mResolveConfigHint.mUseOverrideInsetsForStableBounds
+        final int parentOrientation = mResolveConfigHint.mUseOverrideInsetsForConfig
                 ? stableBoundsOrientation : newParentConfig.orientation;
 
         // If the activity requires a different orientation (either by override or activityInfo),
@@ -9106,7 +9086,7 @@
             return;
         }
 
-        final Rect parentAppBounds = mResolveConfigHint.mUseOverrideInsetsForStableBounds
+        final Rect parentAppBounds = mResolveConfigHint.mUseOverrideInsetsForConfig
                 ? outNonDecorBounds : newParentConfig.windowConfiguration.getAppBounds();
         // TODO(b/182268157): Explore using only one type of parentBoundsWithInsets, either app
         // bounds or stable bounds to unify aspect ratio logic.
@@ -10032,7 +10012,7 @@
             } else {
                 scheduleConfigurationChanged(newMergedOverrideConfig, newActivityWindowInfo);
             }
-            notifyDisplayCompatPolicyAboutConfigurationChange(
+            notifyActivityRefresherAboutConfigurationChange(
                     mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
             return true;
         }
@@ -10099,18 +10079,18 @@
         } else {
             scheduleConfigurationChanged(newMergedOverrideConfig, newActivityWindowInfo);
         }
-        notifyDisplayCompatPolicyAboutConfigurationChange(
+        notifyActivityRefresherAboutConfigurationChange(
                 mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
         return true;
     }
 
-    private void notifyDisplayCompatPolicyAboutConfigurationChange(
+    private void notifyActivityRefresherAboutConfigurationChange(
             Configuration newConfig, Configuration lastReportedConfig) {
-        if (mDisplayContent.mDisplayRotationCompatPolicy == null
+        if (mDisplayContent.mActivityRefresher == null
                 || !shouldBeResumed(/* activeActivity */ null)) {
             return;
         }
-        mDisplayContent.mDisplayRotationCompatPolicy.onActivityConfigurationChanging(
+        mDisplayContent.mActivityRefresher.onActivityConfigurationChanging(
                 this, newConfig, lastReportedConfig);
     }
 
@@ -10862,8 +10842,10 @@
             final Rect filledContainerBounds = mIsInFixedOrientationOrAspectRatioLetterbox
                     ? letterboxedContainerBounds
                     : task != null ? task.getBounds() : display.getBounds();
-            final int filledContainerRotation = task != null
-                    ? task.getConfiguration().windowConfiguration.getRotation()
+            final boolean useActivityRotation = container.hasFixedRotationTransform()
+                    && mIsInFixedOrientationOrAspectRatioLetterbox;
+            final int filledContainerRotation = useActivityRotation
+                    ? container.getWindowConfiguration().getRotation()
                     : display.getConfiguration().windowConfiguration.getRotation();
             final Point dimensions = getRotationZeroDimensions(
                     filledContainerBounds, filledContainerRotation);
@@ -11109,7 +11091,7 @@
      * Otherwise, return the creation time of the top window.
      */
     long getLastWindowCreateTime() {
-        final WindowState window = getWindow(win -> true);
+        final WindowState window = getWindow(alwaysTruePredicate());
         return window != null && window.mAttrs.type != TYPE_BASE_APPLICATION
                 ? window.getCreateTime()
                 : createTime;
diff --git a/services/core/java/com/android/server/wm/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java
new file mode 100644
index 0000000..23a9708
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ActivityRefresher.java
@@ -0,0 +1,131 @@
+/*
+ * 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.server.wm;
+
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
+
+import android.annotation.NonNull;
+import android.app.servertransaction.RefreshCallbackItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.os.RemoteException;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+
+/**
+ * Class that refreshes the activity (through stop/pause -> resume) based on configuration change.
+ *
+ * <p>This class queries all of its {@link Evaluator}s and restarts the activity if any of them
+ * return {@code true} in {@link Evaluator#shouldRefreshActivity}. {@link ActivityRefresher} cycles
+ * through either stop or pause and then resume, based on the global config and per-app override.
+ */
+class ActivityRefresher {
+    // Delay for ensuring that onActivityRefreshed is always called after an activity refresh. The
+    // client process may not always report the event back to the server, such as process is
+    // crashed or got killed.
+    private static final long REFRESH_CALLBACK_TIMEOUT_MS = 2000L;
+
+    @NonNull private final WindowManagerService mWmService;
+    @NonNull private final Handler mHandler;
+    @NonNull private final ArrayList<Evaluator> mEvaluators = new ArrayList<>();
+
+    ActivityRefresher(@NonNull WindowManagerService wmService, @NonNull Handler handler) {
+        mWmService = wmService;
+        mHandler = handler;
+    }
+
+    void addEvaluator(@NonNull Evaluator evaluator) {
+        mEvaluators.add(evaluator);
+    }
+
+    void removeEvaluator(@NonNull Evaluator evaluator) {
+        mEvaluators.remove(evaluator);
+    }
+
+    /**
+     * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle.
+     * This allows to clear cached values in apps (e.g. display or camera rotation) that influence
+     * camera preview and can lead to sideways or stretching issues persisting even after force
+     * rotation.
+     */
+    void onActivityConfigurationChanging(@NonNull ActivityRecord activity,
+            @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
+        if (!shouldRefreshActivity(activity, newConfig, lastReportedConfig)) {
+            return;
+        }
+
+        final boolean cycleThroughStop =
+                mWmService.mLetterboxConfiguration
+                        .isCameraCompatRefreshCycleThroughStopEnabled()
+                        && !activity.mLetterboxUiController
+                        .shouldRefreshActivityViaPauseForCameraCompat();
+
+        activity.mLetterboxUiController.setIsRefreshRequested(true);
+        ProtoLog.v(WM_DEBUG_STATES,
+                "Refreshing activity for freeform camera compatibility treatment, "
+                        + "activityRecord=%s", activity);
+        final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(
+                activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
+        final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(
+                activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
+        try {
+            activity.mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
+                    activity.app.getThread(), refreshCallbackItem, resumeActivityItem);
+            mHandler.postDelayed(() -> {
+                synchronized (mWmService.mGlobalLock) {
+                    onActivityRefreshed(activity);
+                }
+            }, REFRESH_CALLBACK_TIMEOUT_MS);
+        } catch (RemoteException e) {
+            activity.mLetterboxUiController.setIsRefreshRequested(false);
+        }
+    }
+
+    boolean isActivityRefreshing(@NonNull ActivityRecord activity) {
+        return activity.mLetterboxUiController.isRefreshRequested();
+    }
+
+    void onActivityRefreshed(@NonNull ActivityRecord activity) {
+        // TODO(b/333060789): can we tell that refresh did not happen by observing the activity
+        //  state?
+        activity.mLetterboxUiController.setIsRefreshRequested(false);
+    }
+
+    private boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
+            @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
+        return mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled()
+                && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat()
+                && ArrayUtils.find(mEvaluators.toArray(), evaluator ->
+                ((Evaluator) evaluator)
+                        .shouldRefreshActivity(activity, newConfig, lastReportedConfig)) != null;
+    }
+
+    /**
+     * Interface for classes that would like to refresh the recently updated activity, based on the
+     * configuration change.
+     */
+    interface Evaluator {
+        boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
+                @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotCache.java b/services/core/java/com/android/server/wm/ActivitySnapshotCache.java
index 3609837..ed07afd 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotCache.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotCache.java
@@ -30,10 +30,12 @@
     @Override
     void putSnapshot(ActivityRecord ar, TaskSnapshot snapshot) {
         final int hasCode = System.identityHashCode(ar);
+        snapshot.addReference(TaskSnapshot.REFERENCE_CACHE);
         synchronized (mLock) {
             final CacheEntry entry = mRunningCache.get(hasCode);
             if (entry != null) {
                 mAppIdMap.remove(entry.topApp);
+                entry.snapshot.removeReference(TaskSnapshot.REFERENCE_CACHE);
             }
             mAppIdMap.put(ar, hasCode);
             mRunningCache.put(hasCode, new CacheEntry(snapshot, ar));
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 3303367..72b854b 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2113,7 +2113,6 @@
         if (hostTask == null || targetTask != hostTask) {
             return EMBEDDING_DISALLOWED_NEW_TASK;
         }
-
         return taskFragment.isAllowedToEmbedActivity(starting);
     }
 
@@ -2733,7 +2732,7 @@
         // If a target task is specified, try to reuse that one
         if (mOptions != null && mOptions.getLaunchTaskId() != INVALID_TASK_ID) {
             Task launchTask = mRootWindowContainer.anyTaskForId(mOptions.getLaunchTaskId());
-            if (launchTask != null) {
+            if (launchTask != null && launchTask.isLeafTask()) {
                 return launchTask;
             }
             return null;
@@ -2961,23 +2960,9 @@
                 sendCanNotEmbedActivityError(mInTaskFragment, embeddingCheckResult);
             }
         } else {
-            TaskFragment candidateTf = mAddingToTaskFragment != null ? mAddingToTaskFragment : null;
+            TaskFragment candidateTf = mAddingToTaskFragment;
             if (candidateTf == null) {
-                // Puts the activity on the top-most non-isolated navigation TF, unless the
-                // activity is launched from the same TF.
-                final TaskFragment sourceTaskFragment =
-                        mSourceRecord != null ? mSourceRecord.getTaskFragment() : null;
-                final ActivityRecord top = task.getActivity(r -> {
-                    if (!r.canBeTopRunning()) {
-                        return false;
-                    }
-                    final TaskFragment taskFragment = r.getTaskFragment();
-                    return !taskFragment.isIsolatedNav() || (sourceTaskFragment != null
-                            && sourceTaskFragment == taskFragment);
-                });
-                if (top != null) {
-                    candidateTf = top.getTaskFragment();
-                }
+                candidateTf = findCandidateTaskFragment(task);
             }
             if (candidateTf != null && candidateTf.isEmbedded()
                     && canEmbedActivity(candidateTf, mStartActivity, task) == EMBEDDING_ALLOWED) {
@@ -2995,6 +2980,50 @@
     }
 
     /**
+     * Finds a candidate TaskFragment in {@code task} to launch activity, or returns {@code null}
+     * if there's no such a TaskFragment.
+     */
+    @Nullable
+    private TaskFragment findCandidateTaskFragment(@NonNull Task task) {
+        final TaskFragment sourceTaskFragment =
+                mSourceRecord != null ? mSourceRecord.getTaskFragment() : null;
+        for (int i = task.getChildCount() - 1; i >= 0; --i) {
+            final WindowContainer<?> wc = task.getChildAt(i);
+            final ActivityRecord activity = wc.asActivityRecord();
+            if (activity != null) {
+                if (activity.finishing) {
+                    continue;
+                }
+                // Early return if the top child is an Activity.
+                return null;
+            }
+            final TaskFragment taskFragment = wc.asTaskFragment();
+            if (taskFragment == null || taskFragment.isRemovalRequested()) {
+                // Skip if the TaskFragment is going to be finished.
+                continue;
+            }
+            if (taskFragment.getActivity(ActivityRecord::canBeTopRunning) == null) {
+                // Skip if there's no activity in this TF can be top running.
+                continue;
+            }
+            if (taskFragment.isIsolatedNav()) {
+                // Stop here if we reach an isolated navigated TF.
+                return null;
+            }
+            if (sourceTaskFragment != null && sourceTaskFragment == taskFragment) {
+                // Choose the taskFragment launched from even if it's pinned.
+                return taskFragment;
+            }
+            if (taskFragment.isPinned()) {
+                // Skip the pinned TaskFragment.
+                continue;
+            }
+            return taskFragment;
+        }
+        return null;
+    }
+
+    /**
      * Notifies the client side that {@link #mStartActivity} cannot be embedded to
      * {@code taskFragment}.
      */
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 5e95a4b..3aa63af 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -184,6 +184,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ConfigurationInfo;
+import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
@@ -266,6 +267,7 @@
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalManagerRegistry;
 import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 import com.android.server.UiThread;
@@ -7400,7 +7402,26 @@
         }
     }
 
+    /** Cache the return value for {@link #isPip2ExperimentEnabled()} */
+    private static Boolean sIsPip2ExperimentEnabled = null;
+
+    /**
+     * @return {@code true} if PiP2 implementation should be used. Besides the trunk stable flag,
+     * system property can be used to override this read only flag during development.
+     * It's currently limited to phone form factor, i.e., not enabled on ARC / TV.
+     */
     static boolean isPip2ExperimentEnabled() {
-        return Flags.enablePip2Implementation();
+        if (sIsPip2ExperimentEnabled == null) {
+            final FeatureInfo arcFeature = SystemConfig.getInstance().getAvailableFeatures().get(
+                    "org.chromium.arc");
+            final FeatureInfo tvFeature = SystemConfig.getInstance().getAvailableFeatures().get(
+                    FEATURE_LEANBACK);
+            final boolean isArc = arcFeature != null && arcFeature.version >= 0;
+            final boolean isTv = tvFeature != null && tvFeature.version >= 0;
+            sIsPip2ExperimentEnabled = SystemProperties.getBoolean(
+                    "persist.wm_shell.pip2", false)
+                    || (Flags.enablePip2Implementation() && !isArc && !isTv);
+        }
+        return sIsPip2ExperimentEnabled;
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index c74284e..f06d3af 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1704,6 +1704,15 @@
         final Transition transit = task.mTransitionController.requestCloseTransitionIfNeeded(task);
         if (transit != null) {
             transit.collectClose(task);
+            if (!task.mTransitionController.useFullReadyTracking()) {
+                // If a transition was created here, it means this is an isolated removeTask. It's
+                // possible for there to be no consequent operations (eg. this is a multiwindow task
+                // closing so nothing becomes visible in response) so we must "touch" the old ready
+                // tracker so that it doesn't get stuck. However, since the old ready tracker
+                // doesn't support multiple conditions, we have to touch it here at the beginning
+                // before anything that may need it to wait (setReady(false)).
+                transit.setReady(task, true);
+            }
         } else if (task.mTransitionController.isCollecting()) {
             task.mTransitionController.getCollectingTransition().collectClose(task);
         }
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 25885ed..e8faff6 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -96,6 +96,7 @@
     interface TransactionReadyListener {
         void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction);
         default void onTransactionCommitTimeout() {}
+        default void onReadyTimeout() {}
     }
 
     /**
@@ -410,6 +411,7 @@
             if (allFinished && !mReady) {
                 Slog.w(TAG, "Sync group " + mSyncId + " timed-out because not ready. If you see "
                         + "this, please file a bug.");
+                mListener.onReadyTimeout();
             }
             finishNow();
             removeFromDependencies(this);
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index c9703d8..0e4f033 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -1732,7 +1732,10 @@
             // The activity was detached from hierarchy.
             return;
         }
-        activity.mDisplayContent.continueUpdateOrientationForDiffOrienLaunchingApp();
+
+        if (activity.mDisplayContent.isFixedRotationLaunchingApp(activity)) {
+            activity.mDisplayContent.continueUpdateOrientationForDiffOrienLaunchingApp();
+        }
 
         // Restore the launch-behind state.
         activity.mTaskSupervisor.scheduleLaunchTaskBehindComplete(activity.token);
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 0e446b8..62931bb 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -39,6 +39,7 @@
 import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
 import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
 import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskStackToFg;
+import static com.android.window.flags.Flags.balImprovedMetrics;
 import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck;
 import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
 import static com.android.window.flags.Flags.balRequireOptInSameUid;
@@ -805,14 +806,25 @@
      * or {@link #BAL_BLOCK} if the launch should be blocked
      */
     BalVerdict checkBackgroundActivityStartAllowedByCaller(BalState state) {
-        int callingUid = state.mCallingUid;
-        int callingPid = state.mCallingPid;
-        final String callingPackage = state.mCallingPackage;
-        WindowProcessController callerApp = state.mCallerApp;
+        // This is used to block background activity launch even if the app is still
+        // visible to user after user clicking home button.
+
+        // Normal apps with visible app window will be allowed to start activity if app switching
+        // is allowed, or apps like live wallpaper with non app visible window will be allowed.
+        final boolean appSwitchAllowedOrFg = state.mAppSwitchState == APP_SWITCH_ALLOW
+                || state.mAppSwitchState == APP_SWITCH_FG_ONLY;
+        if (appSwitchAllowedOrFg && state.mCallingUidHasAnyVisibleWindow) {
+            return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
+                    /*background*/ false, "callingUid has visible window");
+        }
+        if (mService.mActiveUids.hasNonAppVisibleWindow(state.mCallingUid)) {
+            return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
+                    /*background*/ false, "callingUid has non-app visible window");
+        }
 
         // don't abort for the most important UIDs
-        final int callingAppId = UserHandle.getAppId(callingUid);
-        if (callingUid == Process.ROOT_UID
+        final int callingAppId = UserHandle.getAppId(state.mCallingUid);
+        if (state.mCallingUid == Process.ROOT_UID
                 || callingAppId == Process.SYSTEM_UID
                 || callingAppId == Process.NFC_UID) {
             return new BalVerdict(
@@ -821,7 +833,7 @@
         }
 
         // Always allow home application to start activities.
-        if (isHomeApp(callingUid, callingPackage)) {
+        if (isHomeApp(state.mCallingUid, state.mCallingPackage)) {
             return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
                     /*background*/ false,
                     "Home app");
@@ -836,67 +848,46 @@
                     "Active ime");
         }
 
-        // This is used to block background activity launch even if the app is still
-        // visible to user after user clicking home button.
-        final int appSwitchState = mService.getBalAppSwitchesState();
-
-        // don't abort if the callingUid has a visible window or is a persistent system process
-        final int callingUidProcState = mService.mActiveUids.getUidState(callingUid);
-        final boolean callingUidHasAnyVisibleWindow = mService.hasActiveVisibleWindow(callingUid);
-        final boolean isCallingUidPersistentSystemProcess =
-                callingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
-
-        // Normal apps with visible app window will be allowed to start activity if app switching
-        // is allowed, or apps like live wallpaper with non app visible window will be allowed.
-        final boolean appSwitchAllowedOrFg =
-                appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY;
-        if (appSwitchAllowedOrFg && callingUidHasAnyVisibleWindow) {
-            return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
-                    /*background*/ false, "callingUid has visible window");
-        }
-        if (mService.mActiveUids.hasNonAppVisibleWindow(callingUid)) {
-            return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
-                    /*background*/ false, "callingUid has non-app visible window");
-        }
-
-        if (isCallingUidPersistentSystemProcess) {
+        // don't abort if the callingUid is a persistent system process
+        if (state.mIsCallingUidPersistentSystemProcess) {
             return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
                     /*background*/ false, "callingUid is persistent system process");
         }
 
         // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission
-        if (hasBalPermission(callingUid, callingPid)) {
+        if (hasBalPermission(state.mCallingUid, state.mCallingPid)) {
             return new BalVerdict(BAL_ALLOW_PERMISSION,
                     /*background*/ true,
                     "START_ACTIVITIES_FROM_BACKGROUND permission granted");
         }
         // don't abort if the caller has the same uid as the recents component
-        if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) {
+        if (mSupervisor.mRecentTasks.isCallerRecents(state.mCallingUid)) {
             return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
                     /*background*/ true, "Recents Component");
         }
         // don't abort if the callingUid is the device owner
-        if (mService.isDeviceOwner(callingUid)) {
+        if (mService.isDeviceOwner(state.mCallingUid)) {
             return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
                     /*background*/ true, "Device Owner");
         }
         // don't abort if the callingUid is a affiliated profile owner
-        if (mService.isAffiliatedProfileOwner(callingUid)) {
+        if (mService.isAffiliatedProfileOwner(state.mCallingUid)) {
             return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
                     /*background*/ true, "Affiliated Profile Owner");
         }
         // don't abort if the callingUid has companion device
-        final int callingUserId = UserHandle.getUserId(callingUid);
-        if (mService.isAssociatedCompanionApp(callingUserId, callingUid)) {
+        final int callingUserId = UserHandle.getUserId(state.mCallingUid);
+        if (mService.isAssociatedCompanionApp(callingUserId, state.mCallingUid)) {
             return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
                     /*background*/ true, "Companion App");
         }
         // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission
-        if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) {
+        if (mService.hasSystemAlertWindowPermission(state.mCallingUid, state.mCallingPid,
+                state.mCallingPackage)) {
             Slog.w(
                     TAG,
                     "Background activity start for "
-                            + callingPackage
+                            + state.mCallingPackage
                             + " allowed because SYSTEM_ALERT_WINDOW permission is granted.");
             return new BalVerdict(BAL_ALLOW_SAW_PERMISSION,
                     /*background*/ true, "SYSTEM_ALERT_WINDOW permission is granted");
@@ -905,7 +896,7 @@
         // OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop
         if (isSystemExemptFlagEnabled() && mService.getAppOpsManager().checkOpNoThrow(
                 AppOpsManager.OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
-                callingUid, callingPackage) == AppOpsManager.MODE_ALLOWED) {
+                state.mCallingUid, state.mCallingPackage) == AppOpsManager.MODE_ALLOWED) {
             return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true,
                     "OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop is granted");
         }
@@ -914,7 +905,7 @@
         // That's the case for PendingIntent-based starts, since the creator's process might not be
         // up and alive.
         // Don't abort if the callerApp or other processes of that uid are allowed in any way.
-        BalVerdict callerAppAllowsBal = checkProcessAllowsBal(callerApp, state);
+        BalVerdict callerAppAllowsBal = checkProcessAllowsBal(state.mCallerApp, state);
         if (callerAppAllowsBal.allows()) {
             return callerAppAllowsBal;
         }
@@ -929,13 +920,6 @@
      */
     BalVerdict checkBackgroundActivityStartAllowedBySender(BalState state) {
 
-        if (state.isPendingIntentBalAllowedByPermission()
-                && hasBalPermission(state.mRealCallingUid, state.mRealCallingPid)) {
-            return new BalVerdict(BAL_ALLOW_PERMISSION,
-                    /*background*/ false,
-                    "realCallingUid has BAL permission.");
-        }
-
         // Normal apps with visible app window will be allowed to start activity if app switching
         // is allowed, or apps like live wallpaper with non app visible window will be allowed.
         // The home app can start apps even if app switches are usually disallowed.
@@ -961,6 +945,13 @@
             }
         }
 
+        if (state.isPendingIntentBalAllowedByPermission()
+                && hasBalPermission(state.mRealCallingUid, state.mRealCallingPid)) {
+            return new BalVerdict(BAL_ALLOW_PERMISSION,
+                    /*background*/ false,
+                    "realCallingUid has BAL permission.");
+        }
+
         // if the realCallingUid is a persistent system process, abort if the IntentSender
         // wasn't allowed to start an activity
         if (state.mForcedBalByPiSender.allowsBackgroundActivityStarts()
@@ -1660,28 +1651,64 @@
                             (state.mOriginatingPendingIntent != null));
         }
 
-        @BalCode int code = finalVerdict.getCode();
-        int callingUid = state.mCallingUid;
-        int realCallingUid = state.mRealCallingUid;
-        Intent intent = state.mIntent;
+        if (balImprovedMetrics()) {
+            if (shouldLogStats(finalVerdict, state)) {
+                String activityName;
+                if (shouldLogIntentActivity(finalVerdict, state)) {
+                    Intent intent = state.mIntent;
+                    activityName = intent == null ? "noIntent" // should never happen
+                            : requireNonNull(intent.getComponent()).flattenToShortString();
+                } else {
+                    activityName = "";
+                }
+                writeBalAllowedLog(activityName, finalVerdict.getCode(), state);
+            }
+        } else {
+            @BalCode int code = finalVerdict.getCode();
+            int callingUid = state.mCallingUid;
+            int realCallingUid = state.mRealCallingUid;
+            Intent intent = state.mIntent;
 
-        if (code == BAL_ALLOW_PENDING_INTENT
-                && (callingUid < Process.FIRST_APPLICATION_UID
-                || realCallingUid < Process.FIRST_APPLICATION_UID)) {
-            String activityName = intent != null
-                    ? requireNonNull(intent.getComponent()).flattenToShortString() : "";
-            writeBalAllowedLog(activityName, BAL_ALLOW_PENDING_INTENT,
-                    state);
-        }
-        if (code == BAL_ALLOW_PERMISSION || code == BAL_ALLOW_FOREGROUND
-                || code == BAL_ALLOW_SAW_PERMISSION) {
-            // We don't need to know which activity in this case.
-            writeBalAllowedLog("", code, state);
-
+            if (code == BAL_ALLOW_PENDING_INTENT
+                    && (callingUid < Process.FIRST_APPLICATION_UID
+                    || realCallingUid < Process.FIRST_APPLICATION_UID)) {
+                String activityName = intent != null
+                        ? requireNonNull(intent.getComponent()).flattenToShortString() : "";
+                writeBalAllowedLog(activityName, BAL_ALLOW_PENDING_INTENT,
+                        state);
+            }
+            if (code == BAL_ALLOW_PERMISSION || code == BAL_ALLOW_FOREGROUND
+                    || code == BAL_ALLOW_SAW_PERMISSION) {
+                // We don't need to know which activity in this case.
+                writeBalAllowedLog("", code, state);
+            }
         }
         return finalVerdict;
     }
 
+    @VisibleForTesting
+    boolean shouldLogStats(BalVerdict finalVerdict, BalState state) {
+        if (finalVerdict.blocks()) {
+            return false;
+        }
+        if (!state.isPendingIntent() && finalVerdict.getRawCode() == BAL_ALLOW_VISIBLE_WINDOW) {
+            return false;
+        }
+        if (state.mBalAllowedByPiSender.allowsBackgroundActivityStarts()
+                && state.mResultForRealCaller != null
+                && state.mResultForRealCaller.getRawCode() == BAL_ALLOW_VISIBLE_WINDOW) {
+            return false;
+        }
+        return true;
+    }
+
+    @VisibleForTesting
+    boolean shouldLogIntentActivity(BalVerdict finalVerdict, BalState state) {
+        return finalVerdict.mBasedOnRealCaller
+                ? state.mRealCallingUid < Process.FIRST_APPLICATION_UID
+                : state.mCallingUid < Process.FIRST_APPLICATION_UID;
+    }
+
     @VisibleForTesting void writeBalAllowedLog(String activityName, int code, BalState state) {
         FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED,
                 activityName,
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index 1c59977..6aa0039 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -80,8 +80,8 @@
             LaunchParamsController.LaunchParams outParams) {
 
         if (!canEnterDesktopMode(mContext)) {
-            appendLog("desktop mode is not enabled, continuing");
-            return RESULT_CONTINUE;
+            appendLog("desktop mode is not enabled, skipping");
+            return RESULT_SKIP;
         }
 
         if (task == null) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4147249..e49cb38 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -478,6 +478,8 @@
     final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
     @Nullable
     final CameraStateMonitor mCameraStateMonitor;
+    @Nullable
+    final ActivityRefresher mActivityRefresher;
 
     DisplayFrames mDisplayFrames;
     final DisplayUpdater mDisplayUpdater;
@@ -550,15 +552,6 @@
     // TODO(multi-display): remove some of the usages.
     boolean isDefaultDisplay;
 
-    /** Detect user tapping outside of current focused task bounds .*/
-    // TODO(b/315321016): Remove once pointer event detection is removed from WM.
-    @VisibleForTesting
-    final TaskTapPointerEventListener mTapDetector;
-
-    /** Detect user tapping outside of current focused root task bounds .*/
-    // TODO(b/315321016): Remove once pointer event detection is removed from WM.
-    private Region mTouchExcludeRegion = new Region();
-
     /** Save allocating when calculating rects */
     private final Rect mTmpRect = new Rect();
     private final Rect mTmpRect2 = new Rect();
@@ -571,10 +564,6 @@
 
     final PinnedTaskController mPinnedTaskController;
 
-    final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>();
-    /** A collection of windows that provide tap exclude regions inside of them. */
-    final ArraySet<WindowState> mTapExcludeProvidingWindows = new ArraySet<>();
-
     private final LinkedList<ActivityRecord> mTmpUpdateAllDrawn = new LinkedList();
 
     private final TaskForResizePointSearchResult mTmpTaskForResizePointSearchResult =
@@ -1193,18 +1182,6 @@
                 "PointerEventDispatcher" + mDisplayId, mDisplayId);
         mPointerEventDispatcher = new PointerEventDispatcher(inputChannel);
 
-        if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) {
-            mTapDetector = null;
-        } else {
-            // Tap Listeners are supported for:
-            // 1. All physical displays (multi-display).
-            // 2. VirtualDisplays on VR, AA (and everything else).
-            mTapDetector = new TaskTapPointerEventListener(mWmService, this);
-            registerPointerEventListener(mTapDetector);
-        }
-        if (mWmService.mMousePositionTracker != null) {
-            registerPointerEventListener(mWmService.mMousePositionTracker);
-        }
         if (mWmService.mAtmService.getRecentTasks() != null) {
             registerPointerEventListener(
                     mWmService.mAtmService.getRecentTasks().getInputListener());
@@ -1258,13 +1235,15 @@
                 mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabledAtBuildTime();
         if (shouldCreateDisplayRotationCompatPolicy) {
             mCameraStateMonitor = new CameraStateMonitor(this, mWmService.mH);
+            mActivityRefresher = new ActivityRefresher(mWmService, mWmService.mH);
             mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(
-                    this, mWmService.mH, mCameraStateMonitor);
+                    this, mCameraStateMonitor, mActivityRefresher);
 
             mCameraStateMonitor.startListeningToCameraState();
         } else {
             // These are to satisfy the `final` check.
             mCameraStateMonitor = null;
+            mActivityRefresher = null;
             mDisplayRotationCompatPolicy = null;
         }
 
@@ -2780,7 +2759,7 @@
 
     @Nullable
     Task getTopRootTask() {
-        return getRootTask(t -> true);
+        return getRootTask(alwaysTruePredicate());
     }
 
     /**
@@ -3304,117 +3283,6 @@
                 mTmpTaskForResizePointSearchResult.process(taskDisplayArea, x, y, delta));
     }
 
-    void updateTouchExcludeRegion() {
-        if (mTapDetector == null) {
-            // The touch exclude region is used to detect the region outside of the focused task
-            // so that the tap detector can detect outside touches. Don't calculate the exclude
-            // region when the tap detector is disabled.
-            return;
-        }
-        final Task focusedTask = (mFocusedApp != null ? mFocusedApp.getTask() : null);
-        if (focusedTask == null) {
-            mTouchExcludeRegion.setEmpty();
-        } else {
-            mTouchExcludeRegion.set(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
-            final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
-            mTmpRect.setEmpty();
-            mTmpRect2.setEmpty();
-
-            forAllTasks(t -> { processTaskForTouchExcludeRegion(t, focusedTask, delta); });
-
-            // If we removed the focused task above, add it back and only leave its
-            // outside touch area in the exclusion. TapDetector is not interested in
-            // any touch inside the focused task itself.
-            if (!mTmpRect2.isEmpty()) {
-                mTouchExcludeRegion.op(mTmpRect2, Region.Op.UNION);
-            }
-        }
-        if (mInputMethodWindow != null && mInputMethodWindow.isVisible()) {
-            // If the input method is visible and the user is typing, we don't want these touch
-            // events to be intercepted and used to change focus. This would likely cause a
-            // disappearance of the input method.
-            mInputMethodWindow.getTouchableRegion(mTmpRegion);
-            mTouchExcludeRegion.op(mTmpRegion, Op.UNION);
-        }
-        for (int i = mTapExcludedWindows.size() - 1; i >= 0; i--) {
-            final WindowState win = mTapExcludedWindows.get(i);
-            if (!win.isVisible()) {
-                continue;
-            }
-            win.getTouchableRegion(mTmpRegion);
-            mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION);
-        }
-        amendWindowTapExcludeRegion(mTouchExcludeRegion);
-        mTapDetector.setTouchExcludeRegion(mTouchExcludeRegion);
-    }
-
-    private void processTaskForTouchExcludeRegion(Task task, Task focusedTask, int delta) {
-        if (mTapDetector == null) {
-            // The touch exclude region is used to detect the region outside of the focused task
-            // so that the tap detector can detect outside touches. Don't calculate the exclude
-            // region when the tap detector is disabled.
-        }
-        final ActivityRecord topVisibleActivity = task.getTopVisibleActivity();
-
-        if (topVisibleActivity == null || !topVisibleActivity.hasContentToDisplay()) {
-            return;
-        }
-
-        // Exclusion region is the region that TapDetector doesn't care about.
-        // Here we want to remove all non-focused tasks from the exclusion region.
-        // We also remove the outside touch area for resizing for all freeform
-        // tasks (including the focused).
-        // We save the focused task region once we find it, and add it back at the end.
-        // If the task is root home task and it is resizable and visible (top of its root task),
-        // we want to exclude the root docked task from touch so we need the entire screen area
-        // and not just a small portion which the root home task currently is resized to.
-        if (task.isActivityTypeHome() && task.isVisible() && task.isResizeable()) {
-            task.getDisplayArea().getBounds(mTmpRect);
-        } else {
-            task.getDimBounds(mTmpRect);
-        }
-
-        if (task == focusedTask) {
-            // Add the focused task rect back into the exclude region once we are done
-            // processing root tasks.
-            // NOTE: this *looks* like a no-op, but this usage of mTmpRect2 is expected by
-            //       updateTouchExcludeRegion.
-            mTmpRect2.set(mTmpRect);
-        }
-
-        final boolean isFreeformed = task.inFreeformWindowingMode();
-        if (task != focusedTask || isFreeformed) {
-            if (isFreeformed) {
-                // If the task is freeformed, enlarge the area to account for outside
-                // touch area for resize.
-                mTmpRect.inset(-delta, -delta);
-                // Intersect with display content frame. If we have system decor (status bar/
-                // navigation bar), we want to exclude that from the tap detection.
-                // Otherwise, if the app is partially placed under some system button (eg.
-                // Recents, Home), pressing that button would cause a full series of
-                // unwanted transfer focus/resume/pause, before we could go home.
-                mTmpRect.inset(getInsetsStateController().getRawInsetsState().calculateInsets(
-                        mTmpRect, systemBars() | ime(), false /* ignoreVisibility */));
-            }
-            mTouchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE);
-        }
-    }
-
-    /**
-     * Union the region with all the tap exclude region provided by windows on this display.
-     *
-     * @param inOutRegion The region to be amended.
-     */
-    private void amendWindowTapExcludeRegion(Region inOutRegion) {
-        final Region region = Region.obtain();
-        for (int i = mTapExcludeProvidingWindows.size() - 1; i >= 0; i--) {
-            final WindowState win = mTapExcludeProvidingWindows.valueAt(i);
-            win.getTapExcludeRegion(region);
-            inOutRegion.op(region, Op.UNION);
-        }
-        region.recycle();
-    }
-
     @Override
     void switchUser(int userId) {
         super.switchUser(userId);
@@ -3771,7 +3639,6 @@
         pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight);
         pw.print(subPrefix + "deferred=" + mDeferredRemoval
                 + " mLayoutNeeded=" + mLayoutNeeded);
-        pw.println(" mTouchExcludeRegion=" + mTouchExcludeRegion);
 
         pw.println();
         super.dump(pw, prefix, dumpAll);
@@ -4120,7 +3987,6 @@
         }
 
         getInputMonitor().setFocusedAppLw(newFocus);
-        updateTouchExcludeRegion();
         return true;
     }
 
@@ -5147,7 +5013,7 @@
 
         // This should be called after the insets have been dispatched to clients and we have
         // committed finish drawing windows.
-        mInsetsStateController.getImeSourceProvider().checkShowImePostLayout();
+        mInsetsStateController.getImeSourceProvider().checkAndStartShowImePostLayout();
 
         mLastHasContent = mTmpApplySurfaceChangesTransactionState.displayHasContent;
         if (!inTransition() && !mDisplayRotation.isRotatingSeamlessly()) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index eacf9a3..e0cc064 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -18,8 +18,6 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
-import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
@@ -32,19 +30,14 @@
 import static android.view.Display.TYPE_INTERNAL;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
 import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_CAMERA_COMPAT;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringRes;
-import android.app.servertransaction.RefreshCallbackItem;
-import android.app.servertransaction.ResumeActivityItem;
 import android.content.pm.ActivityInfo.ScreenOrientation;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
-import android.os.Handler;
-import android.os.RemoteException;
 import android.widget.Toast;
 
 import com.android.internal.R;
@@ -64,48 +57,38 @@
  * R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}.
  */
  // TODO(b/261444714): Consider moving Camera-specific logic outside of the WM Core path
-class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStateListener {
+final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStateListener,
+        ActivityRefresher.Evaluator {
 
-    // Delay for updating display rotation after Camera connection is closed. Needed to avoid
-    // rotation flickering when an app is flipping between front and rear cameras or when size
-    // compat mode is restarted.
-    // TODO(b/263114289): Consider associating this delay with a specific activity so that if
-    // the new non-camera activity started on top of the camer one we can rotate faster.
-    private static final int CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS = 2000;
-    // Delay for updating display rotation after Camera connection is opened. This delay is
-    // selected to be long enough to avoid conflicts with transitions on the app's side.
-    // Using a delay < CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS to avoid flickering when an app
-    // is flipping between front and rear cameras (in case requested orientation changes at
-    // runtime at the same time) or when size compat mode is restarted.
-    private static final int CAMERA_OPENED_ROTATION_UPDATE_DELAY_MS =
-            CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS / 2;
-    // Delay for ensuring that onActivityRefreshed is always called after an activity refresh. The
-    // client process may not always report the event back to the server, such as process is
-    // crashed or got killed.
-    private static final int REFRESH_CALLBACK_TIMEOUT_MS = 2000;
-
+    @NonNull
     private final DisplayContent mDisplayContent;
+    @NonNull
     private final WindowManagerService mWmService;
+    @NonNull
     private final CameraStateMonitor mCameraStateMonitor;
-    private final Handler mHandler;
+    @NonNull
+    private final ActivityRefresher mActivityRefresher;
 
     @ScreenOrientation
     private int mLastReportedOrientation = SCREEN_ORIENTATION_UNSET;
 
-    DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent, Handler handler,
-            @NonNull CameraStateMonitor cameraStateMonitor) {
+    DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent,
+            @NonNull CameraStateMonitor cameraStateMonitor,
+            @NonNull ActivityRefresher activityRefresher) {
         // This constructor is called from DisplayContent constructor. Don't use any fields in
         // DisplayContent here since they aren't guaranteed to be set.
-        mHandler = handler;
         mDisplayContent = displayContent;
         mWmService = displayContent.mWmService;
         mCameraStateMonitor = cameraStateMonitor;
         mCameraStateMonitor.addCameraStateListener(this);
+        mActivityRefresher = activityRefresher;
+        mActivityRefresher.addEvaluator(this);
     }
 
     /** Releases camera state listener. */
     void dispose() {
         mCameraStateMonitor.removeCameraStateListener(this);
+        mActivityRefresher.removeEvaluator(this);
     }
 
     /**
@@ -169,47 +152,6 @@
     }
 
     /**
-     * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle.
-     * This allows to clear cached values in apps (e.g. display or camera rotation) that influence
-     * camera preview and can lead to sideways or stretching issues persisting even after force
-     * rotation.
-     */
-    void onActivityConfigurationChanging(ActivityRecord activity, Configuration newConfig,
-            Configuration lastReportedConfig) {
-        if (!isTreatmentEnabledForDisplay()
-                || !mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled()
-                || !shouldRefreshActivity(activity, newConfig, lastReportedConfig)) {
-            return;
-        }
-        boolean cycleThroughStop =
-                mWmService.mLetterboxConfiguration
-                        .isCameraCompatRefreshCycleThroughStopEnabled()
-                && !activity.mLetterboxUiController
-                        .shouldRefreshActivityViaPauseForCameraCompat();
-        try {
-            activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(true);
-            ProtoLog.v(WM_DEBUG_STATES,
-                    "Refreshing activity for camera compatibility treatment, "
-                            + "activityRecord=%s", activity);
-            final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(
-                    activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
-            final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(
-                    activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
-            activity.mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
-                    activity.app.getThread(), refreshCallbackItem, resumeActivityItem);
-            mHandler.postDelayed(
-                    () -> onActivityRefreshed(activity),
-                    REFRESH_CALLBACK_TIMEOUT_MS);
-        } catch (RemoteException e) {
-            activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
-        }
-    }
-
-    void onActivityRefreshed(@NonNull ActivityRecord activity) {
-        activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
-    }
-
-    /**
      * Notifies that animation in {@link ScreenRotationAnimation} has finished.
      *
      * <p>This class uses this signal as a trigger for notifying the user about forced rotation
@@ -276,14 +218,16 @@
 
     // Refreshing only when configuration changes after rotation or camera split screen aspect ratio
     // treatment is enabled
-    private boolean shouldRefreshActivity(ActivityRecord activity, Configuration newConfig,
-            Configuration lastReportedConfig) {
+    @Override
+    public boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
+            @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
         final boolean displayRotationChanged = (newConfig.windowConfiguration.getDisplayRotation()
                 != lastReportedConfig.windowConfiguration.getDisplayRotation());
-        return (displayRotationChanged
-                || activity.mLetterboxUiController.isCameraCompatSplitScreenAspectRatioAllowed())
+        return isTreatmentEnabledForDisplay()
                 && isTreatmentEnabledForActivity(activity)
-                && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat();
+                && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat()
+                && (displayRotationChanged
+                || activity.mLetterboxUiController.isCameraCompatSplitScreenAspectRatioAllowed());
     }
 
     /**
@@ -310,7 +254,6 @@
                 && activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
     }
 
-
     /**
      * Whether camera compat treatment is applicable for the given activity.
      *
@@ -429,6 +372,6 @@
                 || !mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
             return false;
         }
-        return topActivity.mLetterboxUiController.isRefreshAfterRotationRequested();
+        return mActivityRefresher.isActivityRefreshing(topActivity);
     }
 }
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 8116f68..30f2d0d 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -21,13 +21,11 @@
 import static android.view.View.DRAG_FLAG_GLOBAL_SAME_APPLICATION;
 import static android.view.View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG;
 
-import static com.android.input.flags.Flags.enablePointerChoreographer;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.NonNull;
-import android.app.ActivityManager;
 import android.content.ClipData;
 import android.content.Context;
 import android.hardware.input.InputManagerGlobal;
@@ -266,16 +264,12 @@
 
                 final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
                 mDragState.broadcastDragStartedLocked(touchX, touchY);
-                if (enablePointerChoreographer()) {
-                    if ((touchSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
-                        InputManagerGlobal.getInstance().setPointerIcon(
-                                PointerIcon.getSystemIcon(
-                                        mService.mContext, PointerIcon.TYPE_GRABBING),
-                                mDragState.mDisplayContent.getDisplayId(), touchDeviceId,
-                                touchPointerId, mDragState.getInputToken());
-                    }
-                } else {
-                    mDragState.overridePointerIconLocked(touchSource);
+                if ((touchSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
+                    InputManagerGlobal.getInstance().setPointerIcon(
+                            PointerIcon.getSystemIcon(
+                                    mService.mContext, PointerIcon.TYPE_GRABBING),
+                            mDragState.mDisplayContent.getDisplayId(), touchDeviceId,
+                            touchPointerId, mDragState.getInputToken());
                 }
                 // remember the thumb offsets for later
                 mDragState.mThumbOffsetX = thumbCenterX;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 5ed343a..72ae64c 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -45,7 +45,6 @@
 import android.content.ClipDescription;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.hardware.input.InputManagerGlobal;
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
@@ -58,9 +57,7 @@
 import android.view.DragEvent;
 import android.view.InputApplicationHandle;
 import android.view.InputChannel;
-import android.view.InputDevice;
 import android.view.InputWindowHandle;
-import android.view.PointerIcon;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowManager;
@@ -110,7 +107,6 @@
     boolean mCrossProfileCopyAllowed;
     ClipData mData;
     ClipDescription mDataDescription;
-    int mTouchSource;
     boolean mDragResult;
     boolean mRelinquishDragSurfaceToDropTarget;
     float mAnimatedScale = 1.0f;
@@ -263,12 +259,6 @@
             Trace.instant(TRACE_TAG_WINDOW_MANAGER, "DragDropController#DRAG_ENDED");
         }
 
-        // Take the cursor back if it has been changed.
-        if (isFromSource(InputDevice.SOURCE_MOUSE)) {
-            mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY);
-            mTouchSource = 0;
-        }
-
         // Clear the internal variables.
         if (mInputSurface != null) {
             mTransaction.remove(mInputSurface).apply();
@@ -762,18 +752,6 @@
         return animator;
     }
 
-    private boolean isFromSource(int source) {
-        return (mTouchSource & source) == source;
-    }
-
-    void overridePointerIconLocked(int touchSource) {
-        mTouchSource = touchSource;
-        if (isFromSource(InputDevice.SOURCE_MOUSE)) {
-            // TODO(b/293587049): Pointer Icon Refactor: Set the pointer icon from the drag window.
-            InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_GRABBING);
-        }
-    }
-
     private class AnimationListener
             implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
         @Override
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 092ff3d..e03ff688 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -24,7 +24,6 @@
 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
 import static com.android.server.wm.ImeInsetsSourceProviderProto.IME_TARGET_FROM_IME;
 import static com.android.server.wm.ImeInsetsSourceProviderProto.INSETS_SOURCE_PROVIDER;
-import static com.android.server.wm.ImeInsetsSourceProviderProto.IS_IME_LAYOUT_DRAWN;
 import static com.android.server.wm.WindowManagerService.H.UPDATE_MULTI_WINDOW_STACKS;
 
 import android.annotation.NonNull;
@@ -52,19 +51,26 @@
 
     private static final String TAG = ImeInsetsSourceProvider.class.getSimpleName();
 
-    /** The token tracking the current IME request or {@code null} otherwise. */
+    /** The token tracking the show IME request, non-null only while a show request is pending. */
     @Nullable
-    private ImeTracker.Token mImeRequesterStatsToken;
+    private ImeTracker.Token mStatsToken;
+    /** The target that requested to show the IME, non-null only while a show request is pending. */
+    @Nullable
     private InsetsControlTarget mImeRequester;
-    private Runnable mShowImeRunner;
-    private boolean mIsImeLayoutDrawn;
+    /** @see #isImeShowing() */
     private boolean mImeShowing;
+    /** The latest received insets source. */
     private final InsetsSource mLastSource = new InsetsSource(ID_IME, WindowInsets.Type.ime());
 
     /** @see #setFrozen(boolean) */
     private boolean mFrozen;
 
-    /** @see #setServerVisible(boolean) */
+    /**
+     * The server visibility of the source provider's window container. This is out of sync with
+     * {@link InsetsSourceProvider#mServerVisible} while {@link #mFrozen} is {@code true}.
+     *
+     * @see #setServerVisible
+     */
     private boolean mServerVisible;
 
     ImeInsetsSourceProvider(@NonNull InsetsSource source,
@@ -73,6 +79,7 @@
         super(source, stateController, displayContent);
     }
 
+    @Nullable
     @Override
     InsetsSourceControl getControl(InsetsControlTarget target) {
         final InsetsSourceControl control = super.getControl(target);
@@ -124,9 +131,9 @@
     /**
      * Freeze IME insets source state when required.
      *
-     * When setting {@param frozen} as {@code true}, the IME insets provider will freeze the
+     * <p>When setting {@param frozen} as {@code true}, the IME insets provider will freeze the
      * current IME insets state and pending the IME insets state update until setting
-     * {@param frozen} as {@code false}.
+     * {@param frozen} as {@code false}.</p>
      */
     void setFrozen(boolean frozen) {
         if (mFrozen == frozen) {
@@ -223,27 +230,29 @@
     /**
      * Called from {@link WindowManagerInternal#showImePostLayout}
      * when {@link android.inputmethodservice.InputMethodService} requests to show IME
-     * on {@param imeTarget}.
+     * on the given control target.
      *
-     * @param imeTarget imeTarget on which IME request is coming from.
+     * @param imeTarget  the control target on which the IME request is coming from.
      * @param statsToken the token tracking the current IME request.
      */
-    void scheduleShowImePostLayout(InsetsControlTarget imeTarget,
+    void scheduleShowImePostLayout(@NonNull InsetsControlTarget imeTarget,
             @NonNull ImeTracker.Token statsToken) {
-        if (mImeRequesterStatsToken != null) {
-            // Cancel the pre-existing stats token, if any.
-            // Log state on pre-existing request cancel.
-            logShowImePostLayoutState(false /* aborted */);
-            ImeTracker.forLogging().onCancelled(
-                    mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+        if (mImeRequester == null) {
+            // Start tracing only on initial scheduled show IME request, to record end-to-end time.
+            Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
+        } else {
+            // We already have a scheduled show IME request, cancel the previous statsToken and
+            // continue with the new one.
+            logIsScheduledAndReadyToShowIme(false /* aborted */);
+            ImeTracker.forLogging().onCancelled(mStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
         }
-        mImeRequesterStatsToken = statsToken;
-        boolean targetChanged = isTargetChangedWithinActivity(imeTarget);
+        final boolean targetChanged = isTargetChangedWithinActivity(imeTarget);
         mImeRequester = imeTarget;
+        mStatsToken = statsToken;
         if (targetChanged) {
             // target changed, check if new target can show IME.
             ProtoLog.d(WM_DEBUG_IME, "IME target changed within ActivityRecord");
-            checkShowImePostLayout();
+            checkAndStartShowImePostLayout();
             // if IME cannot be shown at this time, it is scheduled to be shown.
             // once window that called IMM.showSoftInput() and DisplayContent's ImeTarget match,
             // it will be shown.
@@ -252,79 +261,58 @@
 
         ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeRequester.getWindow() == null
                 ? mImeRequester : mImeRequester.getWindow().getName());
-        mShowImeRunner = () -> {
-            ImeTracker.forLogging().onProgress(mImeRequesterStatsToken,
-                    ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
-            ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner");
-            // Target should still be the same.
-            if (isReadyToShowIme()) {
-                ImeTracker.forLogging().onProgress(mImeRequesterStatsToken,
-                        ImeTracker.PHASE_WM_SHOW_IME_READY);
-                final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
-
-                ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s",
-                        target.getWindow() != null ? target.getWindow().getName() : "");
-                setImeShowing(true);
-                target.showInsets(WindowInsets.Type.ime(), true /* fromIme */,
-                        mImeRequesterStatsToken);
-                Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
-                if (target != mImeRequester && mImeRequester != null) {
-                    ProtoLog.w(WM_DEBUG_IME,
-                            "showInsets(ime) was requested by different window: %s ",
-                            (mImeRequester.getWindow() != null
-                                    ? mImeRequester.getWindow().getName() : ""));
-                }
-            } else {
-                ImeTracker.forLogging().onFailed(mImeRequesterStatsToken,
-                        ImeTracker.PHASE_WM_SHOW_IME_READY);
-            }
-            // Clear token here so we don't report an error in abortShowImePostLayout().
-            mImeRequesterStatsToken = null;
-            abortShowImePostLayout();
-        };
         mDisplayContent.mWmService.requestTraversal();
     }
 
-    void checkShowImePostLayout() {
-        if (mWindowContainer == null) {
+    /**
+     * Checks whether there is a previously scheduled show IME request and we are ready to show,
+     * in which case also start handling the request.
+     */
+    void checkAndStartShowImePostLayout() {
+        if (!isScheduledAndReadyToShowIme()) {
+            // This can later become ready, so we don't want to cancel the pending request here.
             return;
         }
-        WindowState windowState =  mWindowContainer.asWindowState();
-        if (windowState == null) {
-            throw new IllegalArgumentException("IME insets must be provided by a window.");
+
+        ImeTracker.forLogging().onProgress(mStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+        ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner");
+
+        final InsetsControlTarget target = getControlTarget();
+
+        ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s",
+                target.getWindow() != null ? target.getWindow().getName() : "");
+        setImeShowing(true);
+        target.showInsets(WindowInsets.Type.ime(), true /* fromIme */, mStatsToken);
+        Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
+        if (target != mImeRequester) {
+            ProtoLog.w(WM_DEBUG_IME, "showInsets(ime) was requested by different window: %s ",
+                    (mImeRequester.getWindow() != null ? mImeRequester.getWindow().getName() : ""));
         }
-        // check if IME is drawn
-        if (mIsImeLayoutDrawn
-                || (isReadyToShowIme()
-                && windowState.isDrawn()
-                && !windowState.mGivenInsetsPending)) {
-            mIsImeLayoutDrawn = true;
-            // show IME if InputMethodService requested it to be shown.
-            if (mShowImeRunner != null) {
-                mShowImeRunner.run();
-            }
-        }
+        resetShowImePostLayout();
     }
 
-    /**
-     * Abort any pending request to show IME post layout.
-     */
+    /** Aborts the previously scheduled show IME request. */
     void abortShowImePostLayout() {
-        ProtoLog.d(WM_DEBUG_IME, "abortShowImePostLayout");
-        if (mImeRequesterStatsToken != null) {
-            // Log state on abort.
-            logShowImePostLayoutState(true /* aborted */);
-            ImeTracker.forLogging().onFailed(
-                    mImeRequesterStatsToken, ImeTracker.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT);
-            mImeRequesterStatsToken = null;
+        if (mImeRequester == null) {
+            return;
         }
-        mImeRequester = null;
-        mIsImeLayoutDrawn = false;
-        mShowImeRunner = null;
+        ProtoLog.d(WM_DEBUG_IME, "abortShowImePostLayout");
+        Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
+        logIsScheduledAndReadyToShowIme(true /* aborted */);
+        ImeTracker.forLogging().onFailed(
+                mStatsToken, ImeTracker.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT);
+        resetShowImePostLayout();
     }
 
+    /** Resets the state of the previously scheduled show IME request. */
+    private void resetShowImePostLayout() {
+        mImeRequester = null;
+        mStatsToken = null;
+    }
+
+    /** Checks whether there is a previously scheduled show IME request and we are ready to show. */
     @VisibleForTesting
-    boolean isReadyToShowIme() {
+    boolean isScheduledAndReadyToShowIme() {
         // IMMS#mLastImeTargetWindow always considers focused window as
         // IME target, however DisplayContent#computeImeTarget() can compute
         // a different IME target.
@@ -334,32 +322,47 @@
         // Also, if imeTarget is closing, it would be considered as outdated target.
         // TODO(b/139861270): Remove the child & sublayer check once IMMS is aware of
         //  actual IME target.
+        if (mImeRequester == null) {
+            // No show IME request previously scheduled.
+            return false;
+        }
+        if (!mServerVisible || mFrozen) {
+            // The window container is not available and considered visible.
+            // If frozen, the server visibility is not set until unfrozen.
+            return false;
+        }
+        if (mWindowContainer == null) {
+            // No window container set.
+            return false;
+        }
+        final WindowState windowState = mWindowContainer.asWindowState();
+        if (windowState == null) {
+            throw new IllegalArgumentException("IME insets must be provided by a window.");
+        }
+        if (!windowState.isDrawn() || windowState.mGivenInsetsPending) {
+            // The window is not drawn, or it has pending insets.
+            return false;
+        }
         final InsetsControlTarget dcTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING);
-        if (dcTarget == null || mImeRequester == null) {
-            // Not ready to show if there is no IME layering target, or no IME requester.
+        if (dcTarget == null) {
+            // No IME layering target.
             return false;
         }
         final InsetsControlTarget controlTarget = getControlTarget();
         if (controlTarget == null) {
-            // Not ready to show if there is no IME control target.
+            // No IME control target.
             return false;
         }
         if (controlTarget != mDisplayContent.getImeTarget(IME_TARGET_CONTROL)) {
-            // Not ready to show if control target does not match the one in DisplayContent.
-            return false;
-        }
-        if (!mServerVisible || mFrozen) {
-            // Not ready to show if the window container is not available and considered visible.
-            // If frozen, the server visibility is not set until unfrozen.
+            // The control target does not match the one in DisplayContent.
             return false;
         }
         if (mStateController.hasPendingControls(controlTarget)) {
-            // Not ready to show if control target has pending controls.
+            // The control target has pending controls.
             return false;
         }
         if (getLeash(controlTarget) == null) {
-            // Not ready to show if control target has no source control leash (or leash is not
-            // ready for dispatching).
+            // The control target has no source control leash (or it is not ready for dispatching).
             return false;
         }
 
@@ -371,51 +374,44 @@
                 || isAboveImeLayeringTarget(mImeRequester, dcTarget)
                 || isImeFallbackTarget(mImeRequester)
                 || isImeInputTarget(mImeRequester)
-                || sameAsImeControlTarget();
+                || sameAsImeControlTarget(mImeRequester);
     }
 
     /**
-     * Logs the current state required for showImePostLayout to be triggered.
+     * Logs the current state that can be checked by {@link #isScheduledAndReadyToShowIme}.
      *
-     * @param aborted whether the showImePostLayout was aborted or cancelled.
+     * @param aborted whether the scheduled show IME request was aborted or cancelled.
      */
-    private void logShowImePostLayoutState(boolean aborted) {
+    private void logIsScheduledAndReadyToShowIme(boolean aborted) {
         final var windowState = mWindowContainer != null ? mWindowContainer.asWindowState() : null;
         final var dcTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING);
         final var controlTarget = getControlTarget();
         final var sb = new StringBuilder();
         sb.append("showImePostLayout ").append(aborted ? "aborted" : "cancelled");
-        sb.append(", mWindowContainer is: ");
-        sb.append(mWindowContainer != null ? "non-null" : "null");
+        sb.append(", isScheduledAndReadyToShowIme: ").append(isScheduledAndReadyToShowIme());
+        sb.append(", mImeRequester: ").append(mImeRequester);
+        sb.append(", serverVisible: ").append(mServerVisible);
+        sb.append(", frozen: ").append(mFrozen);
+        sb.append(", mWindowContainer is: ").append(mWindowContainer != null ? "non-null" : "null");
         sb.append(", windowState: ").append(windowState);
         if (windowState != null) {
-            sb.append(", windowState.isDrawn(): ");
-            sb.append(windowState.isDrawn());
-            sb.append(", windowState.mGivenInsetsPending: ");
-            sb.append(windowState.mGivenInsetsPending);
+            sb.append(", isDrawn: ").append(windowState.isDrawn());
+            sb.append(", mGivenInsetsPending: ").append(windowState.mGivenInsetsPending);
         }
-        sb.append(", mIsImeLayoutDrawn: ").append(mIsImeLayoutDrawn);
-        sb.append(", mShowImeRunner: ").append(mShowImeRunner);
-        sb.append(", mImeRequester: ").append(mImeRequester);
         sb.append(", dcTarget: ").append(dcTarget);
         sb.append(", controlTarget: ").append(controlTarget);
-        sb.append("\n");
-        sb.append("isReadyToShowIme(): ").append(isReadyToShowIme());
         if (mImeRequester != null && dcTarget != null && controlTarget != null) {
-            sb.append(", controlTarget == DisplayContent.controlTarget: ");
+            sb.append("\n");
+            sb.append("controlTarget == DisplayContent.controlTarget: ");
             sb.append(controlTarget == mDisplayContent.getImeTarget(IME_TARGET_CONTROL));
             sb.append(", hasPendingControls: ");
             sb.append(mStateController.hasPendingControls(controlTarget));
-            sb.append(", serverVisible: ");
-            sb.append(mServerVisible);
-            sb.append(", frozen: ");
-            sb.append(mFrozen);
-            sb.append(", leash is: ");
-            sb.append(getLeash(controlTarget) != null ? "non-null" : "null");
-            sb.append(", control is: ");
-            sb.append(mControl != null ? "non-null" : "null");
-            sb.append(", mIsLeashReadyForDispatching: ");
-            sb.append(mIsLeashReadyForDispatching);
+            final boolean hasLeash = getLeash(controlTarget) != null;
+            sb.append(", leash is: ").append(hasLeash ? "non-null" : "null");
+            if (!hasLeash) {
+                sb.append(", control is: ").append(mControl != null ? "non-null" : "null");
+                sb.append(", mIsLeashReadyForDispatching: ").append(mIsLeashReadyForDispatching);
+            }
             sb.append(", isImeLayeringTarget: ");
             sb.append(isImeLayeringTarget(mImeRequester, dcTarget));
             sb.append(", isAboveImeLayeringTarget: ");
@@ -425,7 +421,7 @@
             sb.append(", isImeInputTarget: ");
             sb.append(isImeInputTarget(mImeRequester));
             sb.append(", sameAsImeControlTarget: ");
-            sb.append(sameAsImeControlTarget());
+            sb.append(sameAsImeControlTarget(mImeRequester));
         }
         Slog.d(TAG, sb.toString());
     }
@@ -445,19 +441,18 @@
                 && dcTarget.getWindow().mSubLayer > target.getWindow().mSubLayer;
     }
 
-    private boolean isImeFallbackTarget(InsetsControlTarget target) {
+    private boolean isImeFallbackTarget(@NonNull InsetsControlTarget target) {
         return target == mDisplayContent.getImeFallback();
     }
 
-    private boolean isImeInputTarget(InsetsControlTarget target) {
+    private boolean isImeInputTarget(@NonNull InsetsControlTarget target) {
         return target == mDisplayContent.getImeInputTarget();
     }
 
-    private boolean sameAsImeControlTarget() {
-        final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
-        return target == mImeRequester
-                && (mImeRequester.getWindow() == null
-                || !isImeTargetWindowClosing(mImeRequester.getWindow()));
+    private boolean sameAsImeControlTarget(@NonNull InsetsControlTarget target) {
+        final InsetsControlTarget controlTarget = getControlTarget();
+        return controlTarget == target
+                && (target.getWindow() == null || !isImeTargetWindowClosing(target.getWindow()));
     }
 
     private static boolean isImeTargetWindowClosing(@NonNull WindowState win) {
@@ -467,16 +462,15 @@
                     || win.mActivityRecord.willCloseOrEnterPip());
     }
 
-    private boolean isTargetChangedWithinActivity(InsetsControlTarget target) {
+    private boolean isTargetChangedWithinActivity(@NonNull InsetsControlTarget target) {
         // We don't consider the target out of the activity.
-        if (target == null || target.getWindow() == null) {
+        if (target.getWindow() == null) {
             return false;
         }
         return mImeRequester != target
-                && mImeRequester != null && mShowImeRunner != null
+                && mImeRequester != null
                 && mImeRequester.getWindow() != null
-                && mImeRequester.getWindow().mActivityRecord
-                == target.getWindow().mActivityRecord;
+                && mImeRequester.getWindow().mActivityRecord == target.getWindow().mActivityRecord;
     }
     // ---------------------------------------------------------------------------------------
 
@@ -509,7 +503,6 @@
         if (imeRequesterWindow != null) {
             imeRequesterWindow.dumpDebug(proto, IME_TARGET_FROM_IME, logLevel);
         }
-        proto.write(IS_IME_LAYOUT_DRAWN, mIsImeLayoutDrawn);
         proto.end(token);
     }
 
diff --git a/services/core/java/com/android/server/wm/InputConfigAdapter.java b/services/core/java/com/android/server/wm/InputConfigAdapter.java
index 119fafd..ae6e724 100644
--- a/services/core/java/com/android/server/wm/InputConfigAdapter.java
+++ b/services/core/java/com/android/server/wm/InputConfigAdapter.java
@@ -20,8 +20,6 @@
 import android.view.InputWindowHandle.InputConfigFlags;
 import android.view.WindowManager.LayoutParams;
 
-import java.util.List;
-
 /**
  * A helper to determine the {@link InputConfigFlags} that control the behavior of an input window
  * from several WM attributes.
@@ -47,7 +45,7 @@
      * input configurations that can be mapped directly from a corresponding LayoutParams input
      * feature.
      */
-    private static final List<FlagMapping> INPUT_FEATURE_TO_CONFIG_MAP = List.of(
+    private static final FlagMapping[] INPUT_FEATURE_TO_CONFIG_MAP = {
             new FlagMapping(
                     LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL,
                     InputConfig.NO_INPUT_CHANNEL, false /* inverted */),
@@ -59,7 +57,8 @@
                     InputConfig.SPY, false /* inverted */),
             new FlagMapping(
                     LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY,
-                    InputConfig.SENSITIVE_FOR_PRIVACY, false /* inverted */));
+                    InputConfig.SENSITIVE_FOR_PRIVACY, false /* inverted */)
+    };
 
     @InputConfigFlags
     private static final int INPUT_FEATURE_TO_CONFIG_MASK =
@@ -72,7 +71,7 @@
      * NOTE: The layout params flag {@link LayoutParams#FLAG_NOT_FOCUSABLE} is not handled by this
      * adapter, and must be handled explicitly.
      */
-    private static final List<FlagMapping> LAYOUT_PARAM_FLAG_TO_CONFIG_MAP = List.of(
+    private static final FlagMapping[] LAYOUT_PARAM_FLAG_TO_CONFIG_MAP = {
             new FlagMapping(
                     LayoutParams.FLAG_NOT_TOUCHABLE,
                     InputConfig.NOT_TOUCHABLE, false /* inverted */),
@@ -84,7 +83,8 @@
                     InputConfig.WATCH_OUTSIDE_TOUCH, false /* inverted */),
             new FlagMapping(
                     LayoutParams.FLAG_SLIPPERY,
-                    InputConfig.SLIPPERY, false /* inverted */));
+                    InputConfig.SLIPPERY, false /* inverted */)
+    };
 
     @InputConfigFlags
     private static final int LAYOUT_PARAM_FLAG_TO_CONFIG_MASK =
@@ -119,7 +119,7 @@
     }
 
     @InputConfigFlags
-    private static int applyMapping(int flags, List<FlagMapping> flagToConfigMap) {
+    private static int applyMapping(int flags, FlagMapping[] flagToConfigMap) {
         int inputConfig = 0;
         for (final FlagMapping mapping : flagToConfigMap) {
             final boolean flagSet = (flags & mapping.mFlag) != 0;
@@ -131,7 +131,7 @@
     }
 
     @InputConfigFlags
-    private static int computeMask(List<FlagMapping> flagToConfigMap) {
+    private static int computeMask(FlagMapping[] flagToConfigMap) {
         int mask = 0;
         for (final FlagMapping mapping : flagToConfigMap) {
             mask |= mapping.mInputConfig;
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index a84ebd9..22ca82a 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -290,22 +290,6 @@
         }
     }
 
-    @Override
-    public void notifyPointerDisplayIdChanged(int displayId, float x, float y) {
-        synchronized (mService.mGlobalLock) {
-            mService.setMousePointerDisplayId(displayId);
-            if (displayId == Display.INVALID_DISPLAY) return;
-
-            final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
-            if (dc == null) {
-                Slog.wtf(TAG, "The mouse pointer was moved to display " + displayId
-                        + " that does not have a valid DisplayContent.");
-                return;
-            }
-            mService.restorePointerIconLocked(dc, x, y);
-        }
-    }
-
     /** Waits until the built-in input devices have been configured. */
     public boolean waitForInputDevicesReady(long timeoutMillis) {
         synchronized (mInputDevicesReadyMonitor) {
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 4400ed2..2288fe9 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -198,7 +198,7 @@
             if (mControllable) {
                 mWindowContainer.setControllableInsetProvider(this);
                 if (mPendingControlTarget != mControlTarget) {
-                    updateControlForTarget(mPendingControlTarget, true /* force */);
+                    mStateController.notifyControlTargetChanged(mPendingControlTarget, this);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index dfee164..7a1f57b 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -389,7 +389,7 @@
             newControlTargets.clear();
             // Check for and try to run the scheduled show IME request (if it exists), as we
             // now applied the surface transaction and notified the target of the new control.
-            getImeSourceProvider().checkShowImePostLayout();
+            getImeSourceProvider().checkAndStartShowImePostLayout();
         });
     }
 
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 5aa0ed7..b5af806 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -19,13 +19,13 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
+import android.annotation.DimenRes;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Color;
 import android.provider.DeviceConfig;
-import android.util.Slog;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -33,6 +33,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.function.Function;
+import java.util.function.IntSupplier;
 
 /** Reads letterbox configs from resources and controls their overrides at runtime. */
 final class LetterboxConfiguration {
@@ -265,6 +266,12 @@
     // unresizable apps
     private boolean mIsDisplayAspectRatioEnabledForFixedOrientationLetterbox;
 
+    // Supplier for the value in pixel to consider when detecting vertical thin letterboxing
+    private final DimenPxIntSupplier mThinLetterboxWidthPxSupplier;
+
+    // Supplier for the value in pixel to consider when detecting horizontal thin letterboxing
+    private final DimenPxIntSupplier mThinLetterboxHeightPxSupplier;
+
     // Allows to enable letterboxing strategy for translucent activities ignoring flags.
     private boolean mTranslucentLetterboxingOverrideEnabled;
 
@@ -301,6 +308,34 @@
     // Flags dynamically updated with {@link android.provider.DeviceConfig}.
     @NonNull private final SynchedDeviceConfig mDeviceConfig;
 
+    // Cached version of IntSupplier customised to evaluate new dimen in pixels
+    // when density changes
+    private static class DimenPxIntSupplier implements IntSupplier {
+
+        @NonNull
+        private final Context mContext;
+
+        private final int mResourceId;
+
+        private float mLastDensity = Float.MIN_VALUE;
+        private int mValue = 0;
+
+        private DimenPxIntSupplier(@NonNull Context context, @DimenRes int resourceId) {
+            mContext = context;
+            mResourceId = resourceId;
+        }
+
+        @Override
+        public int getAsInt() {
+            final float newDensity = mContext.getResources().getDisplayMetrics().density;
+            if (newDensity != mLastDensity) {
+                mLastDensity = newDensity;
+                mValue = mContext.getResources().getDimensionPixelSize(mResourceId);
+            }
+            return mValue;
+        }
+    }
+
     LetterboxConfiguration(@NonNull final Context systemUiContext) {
         this(systemUiContext, new LetterboxConfigurationPersister(
                 () -> readLetterboxHorizontalReachabilityPositionFromConfig(
@@ -359,6 +394,11 @@
         mIsPolicyForIgnoringRequestedOrientationEnabled = mContext.getResources().getBoolean(
                 R.bool.config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled);
 
+        mThinLetterboxWidthPxSupplier = new DimenPxIntSupplier(mContext,
+                R.dimen.config_letterboxThinLetterboxWidthDp);
+        mThinLetterboxHeightPxSupplier = new DimenPxIntSupplier(mContext,
+                R.dimen.config_letterboxThinLetterboxHeightDp);
+
         mLetterboxConfigurationPersister = letterboxConfigurationPersister;
         mLetterboxConfigurationPersister.start();
 
@@ -1129,6 +1169,24 @@
     }
 
     /**
+     * @return Width in pixel about the padding to use to understand if the letterbox for an
+     *         activity is thin. If the available space has width W and the app has width w, this
+     *         is the maximum value for (W - w) / 2 to be considered for a thin letterboxed app.
+     */
+    int getThinLetterboxWidthPx() {
+        return mThinLetterboxWidthPxSupplier.getAsInt();
+    }
+
+    /**
+     * @return Height in pixel about the padding to use to understand if a letterbox is thin.
+     *         If the available space has height H and the app has height h, this is the maximum
+     *         value for (H - h) / 2 to be considered for a thin letterboxed app.
+     */
+    int getThinLetterboxHeightPx() {
+        return mThinLetterboxHeightPxSupplier.getAsInt();
+    }
+
+    /**
      * Overrides whether using split screen aspect ratio as a default aspect ratio for unresizable
      * apps.
      */
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index b38e666..16d7b4f 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -260,7 +260,7 @@
     // Whether activity "refresh" was requested but not finished in
     // ActivityRecord#activityResumedLocked following the camera compat force rotation in
     // DisplayRotationCompatPolicy.
-    private boolean mIsRefreshAfterRotationRequested;
+    private boolean mIsRefreshRequested;
 
     @NonNull
     private final OptProp mIgnoreRequestedOrientationOptProp;
@@ -571,15 +571,14 @@
     }
 
     /**
-     * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}
-     * following the camera compat force rotation in {@link DisplayRotationCompatPolicy}.
+     * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}.
      */
-    boolean isRefreshAfterRotationRequested() {
-        return mIsRefreshAfterRotationRequested;
+    boolean isRefreshRequested() {
+        return mIsRefreshRequested;
     }
 
-    void setIsRefreshAfterRotationRequested(boolean isRequested) {
-        mIsRefreshAfterRotationRequested = isRequested;
+    void setIsRefreshRequested(boolean isRequested) {
+        mIsRefreshRequested = isRequested;
     }
 
     boolean isOverrideRespectRequestedOrientationEnabled() {
@@ -989,6 +988,10 @@
         }
     }
 
+    boolean isLetterboxEducationEnabled() {
+        return mLetterboxConfiguration.getIsEducationEnabled();
+    }
+
     /**
      * Whether we use split screen aspect ratio for the activity when camera compat treatment
      * is active because the corresponding config is enabled and activity supports resizing.
@@ -1024,6 +1027,67 @@
         return getSplitScreenAspectRatio();
     }
 
+    /**
+     * @return {@value true} if the resulting app is letterboxed in a way defined as thin.
+     */
+    boolean isVerticalThinLetterboxed() {
+        final int thinHeight = mLetterboxConfiguration.getThinLetterboxHeightPx();
+        if (thinHeight < 0) {
+            return false;
+        }
+        final Task task = mActivityRecord.getTask();
+        if (task == null) {
+            return false;
+        }
+        final int padding = Math.abs(
+                task.getBounds().height() - mActivityRecord.getBounds().height()) / 2;
+        return padding <= thinHeight;
+    }
+
+    /**
+     * @return {@value true} if the resulting app is pillarboxed in a way defined as thin.
+     */
+    boolean isHorizontalThinLetterboxed() {
+        final int thinWidth = mLetterboxConfiguration.getThinLetterboxWidthPx();
+        if (thinWidth < 0) {
+            return false;
+        }
+        final Task task = mActivityRecord.getTask();
+        if (task == null) {
+            return false;
+        }
+        final int padding = Math.abs(
+                task.getBounds().width() - mActivityRecord.getBounds().width()) / 2;
+        return padding <= thinWidth;
+    }
+
+
+    /**
+     * @return {@value true} if the vertical reachability should be allowed in case of
+     * thin letteboxing
+     */
+    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 vertical reachability should be enabled in case of
+     * thin letteboxing
+     */
+    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();
+    }
+
     float getSplitScreenAspectRatio() {
         // Getting the same aspect ratio that apps get in split screen.
         final DisplayArea displayArea = mActivityRecord.getDisplayArea();
@@ -1263,6 +1327,9 @@
      * </ul>
      */
     private boolean isHorizontalReachabilityEnabled(Configuration parentConfiguration) {
+        if (!allowHorizontalReachabilityForThinLetterbox()) {
+            return false;
+        }
         // Use screen resolved bounds which uses resolved bounds or size compat bounds
         // as activity bounds can sometimes be empty
         final Rect opaqueActivityBounds = hasInheritedLetterboxBehavior()
@@ -1298,6 +1365,9 @@
      * </ul>
      */
     private boolean isVerticalReachabilityEnabled(Configuration parentConfiguration) {
+        if (!allowVerticalReachabilityForThinLetterbox()) {
+            return false;
+        }
         // Use screen resolved bounds which uses resolved bounds or size compat bounds
         // as activity bounds can sometimes be empty
         final Rect opaqueActivityBounds = hasInheritedLetterboxBehavior()
@@ -1566,6 +1636,8 @@
         if (!shouldShowLetterboxUi) {
             return;
         }
+        pw.println(prefix + "  isVerticalThinLetterboxed=" + isVerticalThinLetterboxed());
+        pw.println(prefix + "  isHorizontalThinLetterboxed=" + isHorizontalThinLetterboxed());
         pw.println(prefix + "  letterboxBackgroundColor=" + Integer.toHexString(
                 getLetterboxBackgroundColor().toArgb()));
         pw.println(prefix + "  letterboxBackgroundType="
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index ce47f5c..60454fc 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -18,6 +18,7 @@
 yunfanc@google.com
 wilsonshih@google.com
 jiamingliu@google.com
+pdwilliams@google.com
 
 # Files related to background activity launches
 per-file Background*Start* = set noparent
diff --git a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
index 498182d..3606a34 100644
--- a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
+++ b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
@@ -41,8 +41,12 @@
 
     PerfettoTransitionTracer() {
         Producer.init(InitArguments.DEFAULTS);
-        mDataSource.register(
-                new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT));
+        DataSourceParams params =
+                new DataSourceParams.Builder()
+                        .setBufferExhaustedPolicy(
+                                PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)
+                        .build();
+        mDataSource.register(params);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 8e78d25..72f592b 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -64,7 +64,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.ArraySet;
@@ -708,26 +707,6 @@
         }
     }
 
-    /**
-     * Removes the oldest recent task that is compatible with the given one. This is possible if
-     * the task windowing mode changed after being added to the Recents.
-     */
-    void removeCompatibleRecentTask(Task task) {
-        final int taskIndex = mTasks.indexOf(task);
-        if (taskIndex < 0) {
-            return;
-        }
-
-        final int candidateIndex = findRemoveIndexForTask(task, false /* includingSelf */);
-        if (candidateIndex == -1) {
-            // Nothing to trim
-            return;
-        }
-
-        final Task taskToRemove = taskIndex > candidateIndex ? task : mTasks.get(candidateIndex);
-        remove(taskToRemove);
-    }
-
     void removeTasksByPackageName(String packageName, int userId) {
         for (int i = mTasks.size() - 1; i >= 0; --i) {
             final Task task = mTasks.get(i);
@@ -1615,10 +1594,6 @@
      * list (if any).
      */
     private int findRemoveIndexForAddTask(Task task) {
-        return findRemoveIndexForTask(task, true /* includingSelf */);
-    }
-
-    private int findRemoveIndexForTask(Task task, boolean includingSelf) {
         final int recentsCount = mTasks.size();
         final Intent intent = task.intent;
         final boolean document = intent != null && intent.isDocument();
@@ -1674,8 +1649,6 @@
                     // existing task
                     continue;
                 }
-            } else if (!includingSelf) {
-                continue;
             }
             return i;
         }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 6003c1b..be8c2ae 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -911,7 +911,6 @@
             dc.getInputMonitor().updateInputWindowsLw(true /*force*/);
             dc.updateSystemGestureExclusion();
             dc.updateKeepClearAreas();
-            dc.updateTouchExcludeRegion();
         });
 
         // Check to see if we are now in a state where the screen should
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index bb86460..3b3eeb4 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -740,16 +740,6 @@
     }
 
     @Override
-    public void updatePointerIcon(IWindow window) {
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            mService.updatePointerIcon(window);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    @Override
     public void updateTapExcludeRegion(IWindow window, Region region) {
         final long identity = Binder.clearCallingIdentity();
         try {
diff --git a/services/core/java/com/android/server/wm/SnapshotCache.java b/services/core/java/com/android/server/wm/SnapshotCache.java
index 8680436..1e6ee7d 100644
--- a/services/core/java/com/android/server/wm/SnapshotCache.java
+++ b/services/core/java/com/android/server/wm/SnapshotCache.java
@@ -92,6 +92,7 @@
             if (entry != null) {
                 mAppIdMap.remove(entry.topApp);
                 mRunningCache.remove(id);
+                entry.snapshot.removeReference(TaskSnapshot.REFERENCE_CACHE);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index 3578971..42ca7b4 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -253,6 +253,7 @@
                 PersistInfoProvider provider) {
             super(provider, userId);
             mId = id;
+            snapshot.addReference(TaskSnapshot.REFERENCE_PERSIST);
             mSnapshot = snapshot;
         }
 
@@ -289,6 +290,7 @@
             if (failed) {
                 deleteSnapshot(mId, mUserId, mPersistInfoProvider);
             }
+            mSnapshot.removeReference(TaskSnapshot.REFERENCE_PERSIST);
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 5f13672..a555388 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -20,7 +20,6 @@
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.ActivityTaskManager.INVALID_WINDOWING_MODE;
 import static android.app.ActivityTaskManager.RESIZE_MODE_FORCED;
-import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
 import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
@@ -171,7 +170,6 @@
 import android.view.DisplayInfo;
 import android.view.InsetsState;
 import android.view.RemoteAnimationAdapter;
-import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.view.WindowManager.TransitionOldType;
@@ -299,6 +297,10 @@
     ActivityRecord mTranslucentActivityWaiting = null;
     ArrayList<ActivityRecord> mUndrawnActivitiesBelowTopTranslucent = new ArrayList<>();
 
+    // The topmost Activity that was converted to translucent for scene transition, which should
+    // be converted from translucent once the transition is completed, or the app died.
+    private ActivityRecord mPendingConvertFromTranslucentActivity = null;
+
     /**
      * Set when we know we are going to be calling updateConfiguration()
      * soon, so want to skip intermediate config checks.
@@ -362,6 +364,10 @@
      * user wants to return to it. */
     private WindowProcessController mRootProcess;
 
+    /** The TF host info are set once the task has ever added an organized task fragment. */
+    int mTaskFragmentHostUid;
+    String mTaskFragmentHostProcessName;
+
     /** Takes on same value as first root activity */
     boolean isPersistable = false;
     int maxRecents;
@@ -438,16 +444,6 @@
     // Id of the previous display the root task was on.
     int mPrevDisplayId = INVALID_DISPLAY;
 
-    /** ID of the display which rotation {@link #mRotation} has. */
-    private int mLastRotationDisplayId = INVALID_DISPLAY;
-
-    /**
-     * Display rotation as of the last time {@link #setBounds(Rect)} was called or this task was
-     * moved to a new display.
-     */
-    @Surface.Rotation
-    private int mRotation;
-
     int mMultiWindowRestoreWindowingMode = INVALID_WINDOWING_MODE;
 
     /**
@@ -458,10 +454,7 @@
      */
     int mLastReportedRequestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 
-    // For comparison with DisplayContent bounds.
-    private Rect mTmpRect = new Rect();
-    // For handling display rotations.
-    private Rect mTmpRect2 = new Rect();
+    private final Rect mTmpRect = new Rect();
 
     // Resize mode of the task. See {@link ActivityInfo#resizeMode}
     // Based on the {@link ActivityInfo#resizeMode} of the root activity.
@@ -1194,9 +1187,6 @@
             updateOverrideConfigurationFromLaunchBounds();
         }
 
-        // Update task bounds if needed.
-        adjustBoundsForDisplayChangeIfNeeded(getDisplayContent());
-
         mRootWindowContainer.updateUIDsPresentOnDisplay();
 
         // Ensure all animations are finished at same time in split-screen mode.
@@ -1468,6 +1458,11 @@
         // passed from Task constructor.
         final TaskFragment childTaskFrag = child.asTaskFragment();
         if (childTaskFrag != null && childTaskFrag.asTask() == null) {
+            if (childTaskFrag.mTaskFragmentOrganizerProcessName != null
+                    && mTaskFragmentHostProcessName == null) {
+                mTaskFragmentHostUid = childTaskFrag.mTaskFragmentOrganizerUid;
+                mTaskFragmentHostProcessName = childTaskFrag.mTaskFragmentOrganizerProcessName;
+            }
             childTaskFrag.setMinDimensions(mMinWidth, mMinHeight);
 
             // The starting window should keep covering its task when a pure TaskFragment is added
@@ -2731,15 +2726,7 @@
             return setBounds(getRequestedOverrideBounds(), bounds);
         }
 
-        int rotation = Surface.ROTATION_0;
-        final DisplayContent displayContent = getRootTask() != null
-                ? getRootTask().getDisplayContent() : null;
-        if (displayContent != null) {
-            rotation = displayContent.getDisplayInfo().rotation;
-        }
-
         final int boundsChange = super.setBounds(bounds);
-        mRotation = rotation;
         updateSurfacePositionNonOrganized();
         return boundsChange;
     }
@@ -2799,10 +2786,6 @@
 
     @Override
     void onDisplayChanged(DisplayContent dc) {
-        final boolean isRootTask = isRootTask();
-        if (!isRootTask && !mCreatedByOrganizer) {
-            adjustBoundsForDisplayChangeIfNeeded(dc);
-        }
         super.onDisplayChanged(dc);
         if (isLeafTask()) {
             final int displayId = (dc != null) ? dc.getDisplayId() : INVALID_DISPLAY;
@@ -2953,48 +2936,6 @@
         return mDragResizing;
     }
 
-    void adjustBoundsForDisplayChangeIfNeeded(final DisplayContent displayContent) {
-        if (displayContent == null) {
-            return;
-        }
-        if (getRequestedOverrideBounds().isEmpty()) {
-            return;
-        }
-        final int displayId = displayContent.getDisplayId();
-        final int newRotation = displayContent.getDisplayInfo().rotation;
-        if (displayId != mLastRotationDisplayId) {
-            // This task is on a display that it wasn't on. There is no point to keep the relative
-            // position if display rotations for old and new displays are different. Just keep these
-            // values.
-            mLastRotationDisplayId = displayId;
-            mRotation = newRotation;
-            return;
-        }
-
-        if (mRotation == newRotation) {
-            // Rotation didn't change. We don't need to adjust the bounds to keep the relative
-            // position.
-            return;
-        }
-
-        // Device rotation changed.
-        // - We don't want the task to move around on the screen when this happens, so update the
-        //   task bounds so it stays in the same place.
-        // - Rotate the bounds and notify activity manager if the task can be resized independently
-        //   from its root task. The root task will take care of task rotation for the other case.
-        mTmpRect2.set(getBounds());
-
-        if (!getWindowConfiguration().canResizeTask()) {
-            setBounds(mTmpRect2);
-            return;
-        }
-
-        displayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
-        if (setBounds(mTmpRect2) != BOUNDS_CHANGE_NONE) {
-            mAtmService.resizeTask(mTaskId, getBounds(), RESIZE_MODE_SYSTEM_SCREEN_ROTATION);
-        }
-    }
-
     /** Cancels any running app transitions associated with the task. */
     void cancelTaskWindowTransition() {
         for (int i = mChildren.size() - 1; i >= 0; --i) {
@@ -3511,6 +3452,8 @@
         // Whether the direct top activity is eligible for letterbox education.
         appCompatTaskInfo.topActivityEligibleForLetterboxEducation = isTopActivityResumed
                 && top.isEligibleForLetterboxEducation();
+        appCompatTaskInfo.isLetterboxEducationEnabled = top != null
+                && top.mLetterboxUiController.isLetterboxEducationEnabled();
         // Whether the direct top activity requested showing camera compat control.
         appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState = isTopActivityResumed
                 ? top.getCameraCompatControlState()
@@ -3525,8 +3468,6 @@
         info.isVisibleRequested = isVisibleRequested();
         info.isSleeping = shouldSleepActivities();
         info.isTopActivityTransparent = top != null && !top.fillsParent();
-        appCompatTaskInfo.isLetterboxDoubleTapEnabled = top != null
-                && top.mLetterboxUiController.isLetterboxDoubleTapEducationEnabled();
         appCompatTaskInfo.topActivityLetterboxVerticalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
         appCompatTaskInfo.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
         appCompatTaskInfo.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET;
@@ -3541,15 +3482,29 @@
             appCompatTaskInfo.topActivityLetterboxWidth = top.getBounds().width();
             appCompatTaskInfo.topActivityLetterboxHeight = top.getBounds().height();
         }
+        // We need to consider if letterboxed or pillarboxed
+        // TODO(b/336807329) Encapsulate reachability logic
+        appCompatTaskInfo.isLetterboxDoubleTapEnabled = top != null
+                && top.mLetterboxUiController.isLetterboxDoubleTapEducationEnabled();
         if (appCompatTaskInfo.isLetterboxDoubleTapEnabled) {
             if (appCompatTaskInfo.isTopActivityPillarboxed()) {
-                // Pillarboxed
-                appCompatTaskInfo.topActivityLetterboxHorizontalPosition =
-                        top.mLetterboxUiController.getLetterboxPositionForHorizontalReachability();
+                if (top.mLetterboxUiController.allowHorizontalReachabilityForThinLetterbox()) {
+                    // Pillarboxed
+                    appCompatTaskInfo.topActivityLetterboxHorizontalPosition =
+                            top.mLetterboxUiController
+                                    .getLetterboxPositionForHorizontalReachability();
+                } else {
+                    appCompatTaskInfo.isLetterboxDoubleTapEnabled = false;
+                }
             } else {
-                // Letterboxed
-                appCompatTaskInfo.topActivityLetterboxVerticalPosition =
-                        top.mLetterboxUiController.getLetterboxPositionForVerticalReachability();
+                if (top.mLetterboxUiController.allowVerticalReachabilityForThinLetterbox()) {
+                    // Letterboxed
+                    appCompatTaskInfo.topActivityLetterboxVerticalPosition =
+                            top.mLetterboxUiController
+                                    .getLetterboxPositionForVerticalReachability();
+                } else {
+                    appCompatTaskInfo.isLetterboxDoubleTapEnabled = false;
+                }
             }
         }
         appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton = top != null
@@ -5037,6 +4992,27 @@
         }
     }
 
+    void abortTranslucentActivityWaiting(@NonNull ActivityRecord r) {
+        if (r != mTranslucentActivityWaiting && r != mPendingConvertFromTranslucentActivity) {
+            return;
+        }
+
+        if (mTranslucentActivityWaiting != null) {
+            if (!mTranslucentActivityWaiting.finishing) {
+                mTranslucentActivityWaiting.setOccludesParent(true);
+            }
+            mTranslucentActivityWaiting = null;
+        }
+        if (mPendingConvertFromTranslucentActivity != null) {
+            if (!mPendingConvertFromTranslucentActivity.finishing) {
+                mPendingConvertFromTranslucentActivity.setOccludesParent(true);
+            }
+            mPendingConvertFromTranslucentActivity = null;
+        }
+        mUndrawnActivitiesBelowTopTranslucent.clear();
+        mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG);
+    }
+
     void checkTranslucentActivityWaiting(ActivityRecord top) {
         if (mTranslucentActivityWaiting != top) {
             mUndrawnActivitiesBelowTopTranslucent.clear();
@@ -5051,10 +5027,19 @@
 
     void convertActivityToTranslucent(ActivityRecord r) {
         mTranslucentActivityWaiting = r;
+        mPendingConvertFromTranslucentActivity = r;
         mUndrawnActivitiesBelowTopTranslucent.clear();
         mHandler.sendEmptyMessageDelayed(TRANSLUCENT_TIMEOUT_MSG, TRANSLUCENT_CONVERSION_TIMEOUT);
     }
 
+    void convertActivityFromTranslucent(ActivityRecord r) {
+        if (r != mPendingConvertFromTranslucentActivity) {
+            Slog.e(TAG, "convertFromTranslucent expects " + mPendingConvertFromTranslucentActivity
+                    + " but is " + r);
+        }
+        mPendingConvertFromTranslucentActivity = null;
+    }
+
     /**
      * Called as activities below the top translucent activity are redrawn. When the last one is
      * redrawn notify the top activity by calling
@@ -6888,7 +6873,7 @@
             mIsBoosted = isBoosted;
             // The client transaction will be applied together with the next assignLayer.
             if (clientTransaction != null) {
-                mDecorSurfaceContainer.mPendingClientTransactions.add(clientTransaction);
+                mPendingClientTransactions.add(clientTransaction);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index 21e7a8d..586f3c3 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -247,6 +247,7 @@
                     break;
                 case NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG:
                     forAllRemoteListeners(mNotifyTaskSnapshotChanged, msg);
+                    ((TaskSnapshot) msg.obj).removeReference(TaskSnapshot.REFERENCE_BROADCAST);
                     break;
                 case NOTIFY_BACK_PRESSED_ON_TASK_ROOT:
                     forAllRemoteListeners(mNotifyBackPressedOnTaskRoot, msg);
@@ -485,6 +486,7 @@
      * Notify listeners that the snapshot of a task has changed.
      */
     void notifyTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) {
+        snapshot.addReference(TaskSnapshot.REFERENCE_BROADCAST);
         final Message msg = mHandler.obtainMessage(NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG,
                 taskId, 0, snapshot);
         forAllLocalListeners(mNotifyTaskSnapshotChanged, msg);
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 2c27b98..eff8315 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -223,7 +223,7 @@
 
     @VisibleForTesting
     Task getTopRootTask() {
-        return getRootTask(t -> true);
+        return getRootTask(alwaysTruePredicate());
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 2b631f7..a3f1503 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -40,6 +40,8 @@
 import static android.os.Process.SYSTEM_UID;
 import static android.os.UserHandle.USER_NULL;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
 import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
@@ -320,9 +322,9 @@
     /** Organizer that organizing this TaskFragment. */
     @Nullable
     private ITaskFragmentOrganizer mTaskFragmentOrganizer;
-    @VisibleForTesting
+
     int mTaskFragmentOrganizerUid = INVALID_UID;
-    private @Nullable String mTaskFragmentOrganizerProcessName;
+    @Nullable String mTaskFragmentOrganizerProcessName;
 
     /** Client assigned unique token for this TaskFragment if this is created by an organizer. */
     @Nullable
@@ -354,14 +356,21 @@
 
     /**
      * Whether the activity navigation should be isolated. That is, Activities cannot be launched
-     * on an isolated TaskFragment, unless the activity is launched from an Activity in the same
-     * isolated TaskFragment, or explicitly requested to be launched to.
-     * <p>
-     * Note that only an embedded TaskFragment can be isolated.
+     * on an isolated TaskFragment unless explicitly requested to be launched to.
      */
     private boolean mIsolatedNav;
 
     /**
+     * Whether the TaskFragment to be pinned.
+     * <p>
+     * If a TaskFragment is pinned, the TaskFragment should be the top-most TaskFragment among other
+     * sibling TaskFragments. Any newly launched and embeddable activity should not be placed in the
+     * pinned TaskFragment, unless the activity is launched from the pinned TaskFragment or
+     * explicitly requested to. Non-embeddable activities are not restricted to.
+     */
+    private boolean mPinned;
+
+    /**
      * Whether the TaskFragment should move to bottom of task when any activity below it is
      * launched in clear top mode.
      */
@@ -485,14 +494,16 @@
      */
     @Nullable
     private WindowProcessController getOrganizerProcessIfDifferent(@Nullable ActivityRecord r) {
-        if ((r == null || mTaskFragmentOrganizerProcessName == null)
-                || (mTaskFragmentOrganizerProcessName.equals(r.processName)
-                && mTaskFragmentOrganizerUid == r.getUid())) {
-            // No organizer or the process is the same.
+        final Task task = getTask();
+        if (r == null || task == null || task.mTaskFragmentHostProcessName == null) {
             return null;
         }
-        return mAtmService.getProcessController(mTaskFragmentOrganizerProcessName,
-                mTaskFragmentOrganizerUid);
+        if (task.mTaskFragmentHostProcessName.equals(r.processName)
+                && task.mTaskFragmentHostUid == r.getUid()) {
+            return null;
+        }
+        return mAtmService.getProcessController(task.mTaskFragmentHostProcessName,
+                task.mTaskFragmentHostUid);
     }
 
     void setAnimationParams(@NonNull TaskFragmentAnimationParams animationParams) {
@@ -513,6 +524,18 @@
     }
 
     /**
+     * Sets whether this TaskFragment {@link #isPinned()}.
+     * <p>
+     * Note that this is no-op if the TaskFragment is not {@link #isEmbedded() embedded}.
+     */
+    void setPinned(boolean pinned) {
+        if (!isEmbedded()) {
+            return;
+        }
+        mPinned = pinned;
+    }
+
+    /**
      * Sets whether transitions are allowed when the TaskFragment is empty. If {@code true},
      * transitions are allowed when the TaskFragment is empty. If {@code false}, transitions
      * will wait until the TaskFragment becomes non-empty or other conditions are met. Default
@@ -530,6 +553,15 @@
         return isEmbedded() && mIsolatedNav;
     }
 
+    /**
+     * Indicates whether this TaskFragment is pinned.
+     *
+     * @see android.window.TaskFragmentOperation#OP_TYPE_SET_PINNED
+     */
+    boolean isPinned() {
+        return isEmbedded() && mPinned;
+    }
+
     TaskFragment getAdjacentTaskFragment() {
         return mAdjacentTaskFragment;
     }
@@ -562,7 +594,6 @@
     }
 
     void setResumedActivity(ActivityRecord r, String reason) {
-        warnForNonLeafTaskFragment("setResumedActivity");
         if (mResumedActivity == r) {
             return;
         }
@@ -848,15 +879,6 @@
         return parentTaskFragment != null ? parentTaskFragment.getOrganizedTaskFragment() : null;
     }
 
-    /**
-     * Simply check and give warning logs if this is not operated on leaf {@link TaskFragment}.
-     */
-    private void warnForNonLeafTaskFragment(String func) {
-        if (!isLeafTaskFragment()) {
-            Slog.w(TAG, func + " on non-leaf task fragment " + this);
-        }
-    }
-
     boolean hasDirectChildActivities() {
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             if (mChildren.get(i).asActivityRecord() != null) {
@@ -933,7 +955,6 @@
      */
     void onActivityStateChanged(ActivityRecord record, ActivityRecord.State state,
             String reason) {
-        warnForNonLeafTaskFragment("onActivityStateChanged");
         if (record == mResumedActivity && state != RESUMED) {
             setResumedActivity(null, reason + " - onActivityStateChanged");
         }
@@ -963,7 +984,6 @@
      * @return {@code true} if the process of the pausing activity is died.
      */
     boolean handleAppDied(WindowProcessController app) {
-        warnForNonLeafTaskFragment("handleAppDied");
         boolean isPausingDied = false;
         if (mPausingActivity != null && mPausingActivity.app == app) {
             ProtoLog.v(WM_DEBUG_STATES, "App died while pausing: %s",
@@ -1221,7 +1241,7 @@
                 // have any running activities, not starting one and not home stack.
                 shouldBeVisible = hasRunningActivities
                         || (starting != null && starting.isDescendantOf(this))
-                        || isActivityTypeHome();
+                        || (isActivityTypeHome() && !isEmbedded());
                 break;
             }
 
@@ -2204,7 +2224,7 @@
     static class ConfigOverrideHint {
         @Nullable DisplayInfo mTmpOverrideDisplayInfo;
         @Nullable ActivityRecord.CompatDisplayInsets mTmpCompatInsets;
-        boolean mUseOverrideInsetsForStableBounds;
+        boolean mUseOverrideInsetsForConfig;
     }
 
     void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@@ -2237,11 +2257,11 @@
             @NonNull Configuration parentConfig, @Nullable ConfigOverrideHint overrideHint) {
         DisplayInfo overrideDisplayInfo = null;
         ActivityRecord.CompatDisplayInsets compatInsets = null;
-        boolean useOverrideInsetsForStableBounds = false;
+        boolean useOverrideInsetsForConfig = false;
         if (overrideHint != null) {
             overrideDisplayInfo = overrideHint.mTmpOverrideDisplayInfo;
             compatInsets = overrideHint.mTmpCompatInsets;
-            useOverrideInsetsForStableBounds = overrideHint.mUseOverrideInsetsForStableBounds;
+            useOverrideInsetsForConfig = overrideHint.mUseOverrideInsetsForConfig;
             if (overrideDisplayInfo != null) {
                 // Make sure the screen related configs can be computed by the provided
                 // display info.
@@ -2305,6 +2325,7 @@
             }
         }
 
+        boolean insetsOverrideApplied = false;
         if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED
                 || inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
             if (!customContainerPolicy && WindowConfiguration.isFloating(windowingMode)) {
@@ -2321,7 +2342,7 @@
                 // The non decor inset are areas that could never be removed in Honeycomb. See
                 // {@link WindowManagerPolicy#getNonDecorInsetsLw}.
                 calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di,
-                        useOverrideInsetsForStableBounds);
+                        useOverrideInsetsForConfig);
             } else {
                 // Apply the given non-decor and stable insets to calculate the corresponding bounds
                 // for screen size of configuration.
@@ -2338,8 +2359,21 @@
                     intersectWithInsetsIfFits(mTmpStableBounds, mTmpBounds,
                             compatInsets.mStableInsets[rotation]);
                     outAppBounds.set(mTmpNonDecorBounds);
+                } else if (useOverrideInsetsForConfig) {
+                    final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+                    final int dw = rotated ? mDisplayContent.mBaseDisplayHeight
+                            : mDisplayContent.mBaseDisplayWidth;
+                    final int dh = rotated ? mDisplayContent.mBaseDisplayWidth
+                            : mDisplayContent.mBaseDisplayHeight;
+                    final DisplayPolicy.DecorInsets.Info decorInsets = mDisplayContent
+                            .getDisplayPolicy().getDecorInsetsInfo(rotation, dw, dh);
+                    mTmpStableBounds.set(outAppBounds);
+                    mTmpStableBounds.inset(decorInsets.mOverrideConfigInsets);
+                    outAppBounds.inset(decorInsets.mOverrideNonDecorInsets);
+                    mTmpNonDecorBounds.set(outAppBounds);
+                    // Record the override apply to avoid duplicated check.
+                    insetsOverrideApplied = true;
                 } else {
-                    // Set to app bounds because it excludes decor insets.
                     mTmpNonDecorBounds.set(outAppBounds);
                     mTmpStableBounds.set(outAppBounds);
                 }
@@ -2381,6 +2415,11 @@
                     // from the parent task would result in applications loaded wrong resource.
                     inOutConfig.smallestScreenWidthDp =
                             Math.min(inOutConfig.screenWidthDp, inOutConfig.screenHeightDp);
+                } else if (insetsOverrideApplied) {
+                    // The smallest width should also consider insets. If the insets are overridden,
+                    // use the overridden value.
+                    inOutConfig.smallestScreenWidthDp =
+                            Math.min(inOutConfig.screenWidthDp, inOutConfig.screenHeightDp);
                 }
                 // otherwise, it will just inherit
             }
@@ -2893,6 +2932,13 @@
         return !mCreatedByOrganizer || mIsRemovalRequested;
     }
 
+    /**
+     * Returns whether this TaskFragment is going to be removed.
+     */
+    boolean isRemovalRequested() {
+        return mIsRemovalRequested;
+    }
+
     @Override
     void removeChild(WindowContainer child) {
         removeChild(child, true /* removeSelfIfPossible */);
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 24b533a..c4e932a 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -365,7 +365,8 @@
 
         @Nullable
         TaskFragmentTransaction.Change prepareActivityReparentedToTask(
-                @NonNull ActivityRecord activity) {
+                @NonNull ActivityRecord activity, @Nullable ActivityRecord nextFillTaskActivity,
+                @Nullable IBinder lastParentTfToken) {
             if (activity.finishing) {
                 Slog.d(TAG, "Reparent activity=" + activity.token + " is finishing");
                 return null;
@@ -408,10 +409,21 @@
             }
             ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Activity=%s reparent to taskId=%d",
                     activity.token, task.mTaskId);
-            return new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENTED_TO_TASK)
-                    .setTaskId(task.mTaskId)
-                    .setActivityIntent(trimIntent(activity.intent))
-                    .setActivityToken(activityToken);
+
+            final TaskFragmentTransaction.Change change =
+                    new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENTED_TO_TASK)
+                            .setTaskId(task.mTaskId)
+                            .setActivityIntent(trimIntent(activity.intent))
+                            .setActivityToken(activityToken);
+            if (lastParentTfToken != null) {
+                change.setTaskFragmentToken(lastParentTfToken);
+            }
+            // Only pass the activity token to the client if it belongs to the same process.
+            if (Flags.fixPipRestoreToOverlay() && nextFillTaskActivity != null
+                    && nextFillTaskActivity.getPid() == mOrganizerPid) {
+                change.setOtherActivityToken(nextFillTaskActivity.token);
+            }
+            return change;
         }
 
         void dispatchTransaction(@NonNull TaskFragmentTransaction transaction) {
@@ -733,13 +745,13 @@
     }
 
     void onActivityReparentedToTask(@NonNull ActivityRecord activity) {
+        final Task task = activity.getTask();
         final ITaskFragmentOrganizer organizer;
         if (activity.mLastTaskFragmentOrganizerBeforePip != null) {
             // If the activity is previously embedded in an organized TaskFragment.
             organizer = activity.mLastTaskFragmentOrganizerBeforePip;
         } else {
             // Find the topmost TaskFragmentOrganizer.
-            final Task task = activity.getTask();
             final TaskFragment[] organizedTf = new TaskFragment[1];
             task.forAllLeafTaskFragments(tf -> {
                 if (tf.isOrganizedTaskFragment()) {
@@ -757,10 +769,24 @@
             Slog.w(TAG, "The last TaskFragmentOrganizer no longer exists");
             return;
         }
-        addPendingEvent(new PendingTaskFragmentEvent.Builder(
+
+        final IBinder parentTfTokenBeforePip = activity.getLastEmbeddedParentTfTokenBeforePip();
+        final PendingTaskFragmentEvent.Builder builder = new PendingTaskFragmentEvent.Builder(
                 PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENTED_TO_TASK, organizer)
                 .setActivity(activity)
-                .build());
+                .setTaskFragmentToken(activity.getLastEmbeddedParentTfTokenBeforePip());
+
+        // Sets the next activity behinds the reparented Activity that's also not in the last
+        // embedded parent TF.
+        final ActivityRecord candidateAssociatedActivity = task.getActivity(
+                ar -> ar != activity && !ar.finishing
+                        && ar.getTaskFragment().getFragmentToken() != parentTfTokenBeforePip);
+        if (candidateAssociatedActivity != null && (!candidateAssociatedActivity.isEmbedded()
+                || candidateAssociatedActivity.getTaskFragment().fillsParent())) {
+            builder.setOtherActivity(candidateAssociatedActivity);
+        }
+
+        addPendingEvent(builder.build());
     }
 
     void onTaskFragmentParentInfoChanged(@NonNull ITaskFragmentOrganizer organizer,
@@ -889,11 +915,16 @@
         @Nullable
         private final TaskFragment mTaskFragment;
         @Nullable
+        private final IBinder mTaskFragmentToken;
+        @Nullable
         private final IBinder mErrorCallbackToken;
         @Nullable
         private final Throwable mException;
         @Nullable
         private final ActivityRecord mActivity;
+        // An additional Activity that's needed to send back to the client other than the mActivity.
+        @Nullable
+        private final ActivityRecord mOtherActivity;
         @Nullable
         private final Task mTask;
         // Set when the event is deferred due to the host task is invisible. The defer time will
@@ -905,17 +936,21 @@
         private PendingTaskFragmentEvent(@EventType int eventType,
                 ITaskFragmentOrganizer taskFragmentOrg,
                 @Nullable TaskFragment taskFragment,
+                @Nullable IBinder taskFragmentToken,
                 @Nullable IBinder errorCallbackToken,
                 @Nullable Throwable exception,
                 @Nullable ActivityRecord activity,
+                @Nullable ActivityRecord otherActivity,
                 @Nullable Task task,
                 @TaskFragmentOperation.OperationType int opType) {
             mEventType = eventType;
             mTaskFragmentOrg = taskFragmentOrg;
             mTaskFragment = taskFragment;
+            mTaskFragmentToken = taskFragmentToken;
             mErrorCallbackToken = errorCallbackToken;
             mException = exception;
             mActivity = activity;
+            mOtherActivity = otherActivity;
             mTask = task;
             mOpType = opType;
         }
@@ -943,12 +978,16 @@
             @Nullable
             private TaskFragment mTaskFragment;
             @Nullable
+            private IBinder mTaskFragmentToken;
+            @Nullable
             private IBinder mErrorCallbackToken;
             @Nullable
             private Throwable mException;
             @Nullable
             private ActivityRecord mActivity;
             @Nullable
+            private ActivityRecord mOtherActivity;
+            @Nullable
             private Task mTask;
             @TaskFragmentOperation.OperationType
             private int mOpType;
@@ -963,6 +1002,11 @@
                 return this;
             }
 
+            Builder setTaskFragmentToken(@Nullable IBinder fragmentToken) {
+                mTaskFragmentToken = fragmentToken;
+                return this;
+            }
+
             Builder setErrorCallbackToken(@Nullable IBinder errorCallbackToken) {
                 mErrorCallbackToken = errorCallbackToken;
                 return this;
@@ -978,6 +1022,11 @@
                 return this;
             }
 
+            Builder setOtherActivity(@NonNull ActivityRecord otherActivity) {
+                mOtherActivity = otherActivity;
+                return this;
+            }
+
             Builder setTask(@NonNull Task task) {
                 mTask = requireNonNull(task);
                 return this;
@@ -990,7 +1039,8 @@
 
             PendingTaskFragmentEvent build() {
                 return new PendingTaskFragmentEvent(mEventType, mTaskFragmentOrg, mTaskFragment,
-                        mErrorCallbackToken, mException, mActivity, mTask, mOpType);
+                        mTaskFragmentToken, mErrorCallbackToken, mException, mActivity,
+                        mOtherActivity, mTask, mOpType);
             }
         }
     }
@@ -1191,7 +1241,8 @@
                 return state.prepareTaskFragmentError(event.mErrorCallbackToken, taskFragment,
                         event.mOpType, event.mException);
             case PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENTED_TO_TASK:
-                return state.prepareActivityReparentedToTask(event.mActivity);
+                return state.prepareActivityReparentedToTask(event.mActivity, event.mOtherActivity,
+                        event.mTaskFragmentToken);
             default:
                 throw new IllegalArgumentException("Unknown TaskFragmentEvent=" + event.mEventType);
         }
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
index b69ac1b..64b9df5 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
@@ -35,9 +35,11 @@
 
     void putSnapshot(Task task, TaskSnapshot snapshot) {
         synchronized (mLock) {
+            snapshot.addReference(TaskSnapshot.REFERENCE_CACHE);
             final CacheEntry entry = mRunningCache.get(task.mTaskId);
             if (entry != null) {
                 mAppIdMap.remove(entry.topApp);
+                entry.snapshot.removeReference(TaskSnapshot.REFERENCE_CACHE);
             }
             final ActivityRecord top = task.getTopMostActivity();
             mAppIdMap.put(top, task.mTaskId);
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
deleted file mode 100644
index ac244c7..0000000
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
-import static android.view.PointerIcon.TYPE_NOT_SPECIFIED;
-import static android.view.PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
-import static android.view.PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
-import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
-
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.hardware.input.InputManagerGlobal;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-import android.view.WindowManagerPolicyConstants.PointerEventListener;
-
-import com.android.server.wm.WindowManagerService.H;
-
-/**
- * 1. Adjust the top most focus display if touch down on some display.
- * 2. Adjust the pointer icon when cursor moves to the task bounds.
- */
-public class TaskTapPointerEventListener implements PointerEventListener {
-
-    private final Region mTouchExcludeRegion = new Region();
-    private final WindowManagerService mService;
-    private final DisplayContent mDisplayContent;
-    private final Rect mTmpRect = new Rect();
-    private int mPointerIconType = TYPE_NOT_SPECIFIED;
-
-    public TaskTapPointerEventListener(WindowManagerService service,
-            DisplayContent displayContent) {
-        // TODO(b/315321016): Remove this class when the flag rollout is complete.
-        if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) {
-            throw new IllegalStateException("TaskTapPointerEventListener should not be used!");
-        }
-        mService = service;
-        mDisplayContent = displayContent;
-    }
-
-    private void restorePointerIcon(int x, int y) {
-        if (mPointerIconType != TYPE_NOT_SPECIFIED) {
-            mPointerIconType = TYPE_NOT_SPECIFIED;
-            // Find the underlying window and ask it to restore the pointer icon.
-            mService.mH.removeMessages(H.RESTORE_POINTER_ICON);
-            mService.mH.obtainMessage(H.RESTORE_POINTER_ICON,
-                    x, y, mDisplayContent).sendToTarget();
-        }
-    }
-
-    @Override
-    public void onPointerEvent(MotionEvent motionEvent) {
-        switch (motionEvent.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN: {
-                final int x;
-                final int y;
-                if (motionEvent.getSource() == InputDevice.SOURCE_MOUSE) {
-                    x = (int) motionEvent.getXCursorPosition();
-                    y = (int) motionEvent.getYCursorPosition();
-                } else {
-                    x = (int) motionEvent.getX();
-                    y = (int) motionEvent.getY();
-                }
-
-                synchronized (this) {
-                    if (!mTouchExcludeRegion.contains(x, y)) {
-                        mService.mTaskPositioningController.handleTapOutsideTask(
-                                mDisplayContent, x, y);
-                    }
-                }
-            }
-            break;
-            case MotionEvent.ACTION_HOVER_ENTER:
-            case MotionEvent.ACTION_HOVER_MOVE: {
-                final int x = (int) motionEvent.getX();
-                final int y = (int) motionEvent.getY();
-                if (mTouchExcludeRegion.contains(x, y)) {
-                    restorePointerIcon(x, y);
-                    break;
-                }
-                final Task task = mDisplayContent.findTaskForResizePoint(x, y);
-                int iconType = TYPE_NOT_SPECIFIED;
-                if (task != null) {
-                    task.getDimBounds(mTmpRect);
-                    if (!mTmpRect.isEmpty() && !mTmpRect.contains(x, y)) {
-                        if (x < mTmpRect.left) {
-                            iconType =
-                                (y < mTmpRect.top) ? TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW :
-                                (y > mTmpRect.bottom) ? TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW :
-                                TYPE_HORIZONTAL_DOUBLE_ARROW;
-                        } else if (x > mTmpRect.right) {
-                            iconType =
-                                (y < mTmpRect.top) ? TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW :
-                                (y > mTmpRect.bottom) ? TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW :
-                                TYPE_HORIZONTAL_DOUBLE_ARROW;
-                        } else if (y < mTmpRect.top || y > mTmpRect.bottom) {
-                            iconType = TYPE_VERTICAL_DOUBLE_ARROW;
-                        }
-                    }
-                }
-                if (mPointerIconType != iconType) {
-                    mPointerIconType = iconType;
-                    if (mPointerIconType == TYPE_NOT_SPECIFIED) {
-                        // Find the underlying window and ask it restore the pointer icon.
-                        mService.mH.removeMessages(H.RESTORE_POINTER_ICON);
-                        mService.mH.obtainMessage(H.RESTORE_POINTER_ICON,
-                                x, y, mDisplayContent).sendToTarget();
-                    } else {
-                        InputManagerGlobal.getInstance()
-                                .setPointerIconType(mPointerIconType);
-                    }
-                }
-            }
-            break;
-            case MotionEvent.ACTION_HOVER_EXIT: {
-                final int x = (int) motionEvent.getX();
-                final int y = (int) motionEvent.getY();
-                restorePointerIcon(x, y);
-            }
-            break;
-        }
-    }
-
-    void setTouchExcludeRegion(Region newRegion) {
-        synchronized (this) {
-           mTouchExcludeRegion.set(newRegion);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 1543263..7ec31d5 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1648,14 +1648,6 @@
         }
 
         if (mController.useFullReadyTracking()) {
-            if (mReadyTracker.mMet.isEmpty()) {
-                Slog.e(TAG, "#" + mSyncId + ": No conditions provided");
-            } else {
-                for (int i = 0; i < mReadyTracker.mConditions.size(); ++i) {
-                    Slog.e(TAG, "#" + mSyncId + ": unmet condition at ready: "
-                            + mReadyTracker.mConditions.get(i));
-                }
-            }
             for (int i = 0; i < mReadyTracker.mMet.size(); ++i) {
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "#%d: Met condition: %s",
                         mSyncId, mReadyTracker.mMet.get(i));
@@ -3360,6 +3352,18 @@
         applyReady();
     }
 
+    @Override
+    public void onReadyTimeout() {
+        if (!mController.useFullReadyTracking()) {
+            Slog.e(TAG, "#" + mSyncId + " readiness timeout, used=" + mReadyTrackerOld.mUsed
+                    + " deferReadyDepth=" + mReadyTrackerOld.mDeferReadyDepth
+                    + " group=" + mReadyTrackerOld.mReadyGroups);
+            return;
+        }
+        Slog.e(TAG, "#" + mSyncId + " met conditions: " + mReadyTracker.mMet);
+        Slog.e(TAG, "#" + mSyncId + " unmet conditions: " + mReadyTracker.mConditions);
+    }
+
     /**
      * Represents a condition that must be met before an associated transition can be considered
      * ready.
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index d70ca02..edbba92 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -116,6 +116,7 @@
 import com.android.server.wm.SurfaceAnimator.Animatable;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
+import com.android.server.wm.utils.AlwaysTruePredicate;
 
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -2019,29 +2020,34 @@
                 callback, boundary, includeBoundary, traverseTopToBottom, boundaryFound);
     }
 
+    @SuppressWarnings("unchecked")
+    static <T> Predicate<T> alwaysTruePredicate() {
+        return (Predicate<T>) AlwaysTruePredicate.INSTANCE;
+    }
+
     ActivityRecord getActivityAbove(ActivityRecord r) {
-        return getActivity((above) -> true, r,
+        return getActivity(alwaysTruePredicate(), r /* boundary */,
                 false /*includeBoundary*/, false /*traverseTopToBottom*/);
     }
 
     ActivityRecord getActivityBelow(ActivityRecord r) {
-        return getActivity((below) -> true, r,
+        return getActivity(alwaysTruePredicate(), r /* boundary */,
                 false /*includeBoundary*/, true /*traverseTopToBottom*/);
     }
 
     ActivityRecord getBottomMostActivity() {
-        return getActivity((r) -> true, false /*traverseTopToBottom*/);
+        return getActivity(alwaysTruePredicate(), false /* traverseTopToBottom */);
     }
 
     ActivityRecord getTopMostActivity() {
-        return getActivity((r) -> true, true /*traverseTopToBottom*/);
+        return getActivity(alwaysTruePredicate(), true /* traverseTopToBottom */);
     }
 
     ActivityRecord getTopActivity(boolean includeFinishing, boolean includeOverlays) {
         // Break down into 4 calls to avoid object creation due to capturing input params.
         if (includeFinishing) {
             if (includeOverlays) {
-                return getActivity((r) -> true);
+                return getActivity(alwaysTruePredicate());
             }
             return getActivity((r) -> !r.isTaskOverlay());
         } else if (includeOverlays) {
@@ -2220,21 +2226,17 @@
         }
     }
 
-    Task getTaskAbove(Task t) {
-        return getTask(
-                (above) -> true, t, false /*includeBoundary*/, false /*traverseTopToBottom*/);
-    }
-
     Task getTaskBelow(Task t) {
-        return getTask((below) -> true, t, false /*includeBoundary*/, true /*traverseTopToBottom*/);
+        return getTask(alwaysTruePredicate(), t /* boundary */,
+                false /* includeBoundary */, true /* traverseTopToBottom */);
     }
 
     Task getBottomMostTask() {
-        return getTask((t) -> true, false /*traverseTopToBottom*/);
+        return getTask(alwaysTruePredicate(), false /* traverseTopToBottom */);
     }
 
     Task getTopMostTask() {
-        return getTask((t) -> true, true /*traverseTopToBottom*/);
+        return getTask(alwaysTruePredicate(), true /* traverseTopToBottom */);
     }
 
     Task getTask(Predicate<Task> callback) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index dbe3d36..b603551 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -81,15 +81,11 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
-import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
 import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
 import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
-import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
 import static android.view.WindowManager.TRANSIT_NONE;
@@ -203,7 +199,6 @@
 import android.hardware.configstore.V1_1.ISurfaceFlingerConfigs;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
-import android.hardware.input.InputManager;
 import android.hardware.input.InputSettings;
 import android.net.Uri;
 import android.os.Binder;
@@ -289,8 +284,6 @@
 import android.view.InsetsState;
 import android.view.KeyEvent;
 import android.view.MagnificationSpec;
-import android.view.MotionEvent;
-import android.view.PointerIcon;
 import android.view.RemoteAnimationAdapter;
 import android.view.ScrollCaptureResponse;
 import android.view.Surface;
@@ -335,6 +328,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.IResultReceiver;
+import com.android.internal.os.TransferPipe;
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.IKeyguardLockedStateListener;
 import com.android.internal.policy.IShortcutService;
@@ -545,13 +539,16 @@
             if (asProto) {
                 return;
             }
+
+            final long timeoutMs = 1000L;
             mAtmService.dumpActivity(fd, pw, /* name= */ "all", /* args= */ new String[]{},
                     /* opti= */ 0,
                     /* dumpAll= */ true,
                     /* dumpVisibleRootTasksOnly= */ true,
                     /* dumpFocusedRootTaskOnly= */ false, INVALID_DISPLAY, UserHandle.USER_ALL,
-                    /* timeout= */ 1000
+                    timeoutMs
             );
+            dumpVisibleWindowClients(fd, pw, timeoutMs);
         }
 
         @Override
@@ -1523,18 +1520,6 @@
         }
     }
 
-    static boolean excludeWindowTypeFromTapOutTask(int windowType) {
-        switch (windowType) {
-            case TYPE_STATUS_BAR:
-            case TYPE_NOTIFICATION_SHADE:
-            case TYPE_NAVIGATION_BAR:
-            case TYPE_INPUT_METHOD_DIALOG:
-            case TYPE_VOLUME_OVERLAY:
-                return true;
-        }
-        return false;
-    }
-
     public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
             int displayId, int requestUserId, @InsetsType int requestedVisibleTypes,
             InputChannel outInputChannel, InsetsState outInsetsState,
@@ -1833,10 +1818,6 @@
                 displayContent.mWinAddedSinceNullFocus.add(win);
             }
 
-            if (excludeWindowTypeFromTapOutTask(type)) {
-                displayContent.mTapExcludedWindows.add(win);
-            }
-
             win.mSession.onWindowAdded(win);
             mWindowMap.put(client.asBinder(), win);
             win.initAppOpsState();
@@ -5716,7 +5697,6 @@
 
         public static final int UPDATE_ANIMATION_SCALE = 51;
         public static final int WINDOW_HIDE_TIMEOUT = 52;
-        public static final int RESTORE_POINTER_ICON = 55;
         public static final int SET_HAS_OVERLAY_UI = 58;
         public static final int ANIMATION_FAILSAFE = 60;
         public static final int RECOMPUTE_FOCUS = 61;
@@ -5949,12 +5929,6 @@
                     }
                     break;
                 }
-                case RESTORE_POINTER_ICON: {
-                    synchronized (mGlobalLock) {
-                        restorePointerIconLocked((DisplayContent)msg.obj, msg.arg1, msg.arg2);
-                    }
-                    break;
-                }
                 case SET_HAS_OVERLAY_UI: {
                     mAmInternal.setHasOverlayUi(msg.arg1, msg.arg2 == 1);
                     break;
@@ -7574,144 +7548,6 @@
         }
     }
 
-    // The mouse position tracker will be obsolete after the Pointer Icon Refactor.
-    // TODO(b/293587049): Remove after the refactoring is fully rolled out.
-    @Nullable
-    final MousePositionTracker mMousePositionTracker =
-            com.android.input.flags.Flags.enablePointerChoreographer() ? null
-                    : new MousePositionTracker();
-
-    private static class MousePositionTracker implements PointerEventListener {
-        private boolean mLatestEventWasMouse;
-        private float mLatestMouseX;
-        private float mLatestMouseY;
-
-        /**
-         * The display that the pointer (mouse cursor) is currently shown on. This is updated
-         * directly by InputManagerService when the pointer display changes.
-         */
-        private int mPointerDisplayId = INVALID_DISPLAY;
-
-        /**
-         * Update the mouse cursor position as a result of a mouse movement.
-         * @return true if the position was successfully updated, false otherwise.
-         */
-        boolean updatePosition(int displayId, float x, float y) {
-            synchronized (this) {
-                mLatestEventWasMouse = true;
-
-                if (displayId != mPointerDisplayId) {
-                    // The display of the position update does not match the display on which the
-                    // mouse pointer is shown, so do not update the position.
-                    return false;
-                }
-                mLatestMouseX = x;
-                mLatestMouseY = y;
-                return true;
-            }
-        }
-
-        void setPointerDisplayId(int displayId) {
-            synchronized (this) {
-                mPointerDisplayId = displayId;
-            }
-        }
-
-        @Override
-        public void onPointerEvent(MotionEvent motionEvent) {
-            if (motionEvent.isFromSource(InputDevice.SOURCE_MOUSE)) {
-                updatePosition(motionEvent.getDisplayId(), motionEvent.getRawX(),
-                        motionEvent.getRawY());
-            } else {
-                synchronized (this) {
-                    mLatestEventWasMouse = false;
-                }
-            }
-        }
-    };
-
-    void updatePointerIcon(IWindow client) {
-        if (mMousePositionTracker == null) {
-            return;
-        }
-        int pointerDisplayId;
-        float mouseX, mouseY;
-
-        synchronized(mMousePositionTracker) {
-            if (!mMousePositionTracker.mLatestEventWasMouse) {
-                return;
-            }
-            mouseX = mMousePositionTracker.mLatestMouseX;
-            mouseY = mMousePositionTracker.mLatestMouseY;
-            pointerDisplayId = mMousePositionTracker.mPointerDisplayId;
-        }
-
-        synchronized (mGlobalLock) {
-            if (mDragDropController.dragDropActiveLocked()) {
-                // Drag cursor overrides the app cursor.
-                return;
-            }
-            WindowState callingWin = windowForClientLocked(null, client, false);
-            if (callingWin == null) {
-                ProtoLog.w(WM_ERROR, "Bad requesting window %s", client);
-                return;
-            }
-            final DisplayContent displayContent = callingWin.getDisplayContent();
-            if (displayContent == null) {
-                return;
-            }
-            if (pointerDisplayId != displayContent.getDisplayId()) {
-                // Do not let the pointer icon be updated by a window on a different display.
-                return;
-            }
-            WindowState windowUnderPointer =
-                    displayContent.getTouchableWinAtPointLocked(mouseX, mouseY);
-            if (windowUnderPointer != callingWin) {
-                return;
-            }
-            try {
-                windowUnderPointer.mClient.updatePointerIcon(
-                        windowUnderPointer.translateToWindowX(mouseX),
-                        windowUnderPointer.translateToWindowY(mouseY));
-            } catch (RemoteException e) {
-                ProtoLog.w(WM_ERROR, "unable to update pointer icon");
-            }
-        }
-    }
-
-    void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) {
-        if (mMousePositionTracker == null) {
-            return;
-        }
-        // Mouse position tracker has not been getting updates while dragging, update it now.
-        if (!mMousePositionTracker.updatePosition(
-                displayContent.getDisplayId(), latestX, latestY)) {
-            // The mouse position could not be updated, so ignore this request.
-            return;
-        }
-
-        WindowState windowUnderPointer =
-                displayContent.getTouchableWinAtPointLocked(latestX, latestY);
-        if (windowUnderPointer != null) {
-            try {
-                windowUnderPointer.mClient.updatePointerIcon(
-                        windowUnderPointer.translateToWindowX(latestX),
-                        windowUnderPointer.translateToWindowY(latestY));
-            } catch (RemoteException e) {
-                ProtoLog.w(WM_ERROR, "unable to restore pointer icon");
-            }
-        } else {
-            mContext.getSystemService(InputManager.class)
-                    .setPointerIconType(PointerIcon.TYPE_DEFAULT);
-        }
-    }
-    void setMousePointerDisplayId(int displayId) {
-        if (mMousePositionTracker == null) {
-            return;
-        }
-        mMousePositionTracker.setPointerDisplayId(displayId);
-    }
-
     /**
      * Update a tap exclude region in the window identified by the provided id. Touches down on this
      * region will not:
@@ -8466,7 +8302,6 @@
                 ImeTracker.forLogging().onProgress(statsToken,
                         ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET);
 
-                Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
                 final InsetsControlTarget controlTarget = imeTarget.getImeControlTarget();
                 imeTarget = controlTarget.getWindow();
                 // If InsetsControlTarget doesn't have a window, it's using remoteControlTarget
@@ -10350,4 +10185,32 @@
         }
         return true;
     }
+
+    /**
+     * Dump ViewRootImpl for visible non-activity windows.
+     */
+    private void dumpVisibleWindowClients(FileDescriptor fd, PrintWriter pw, long timeout) {
+        final ArrayList<WindowState> systemWindows = new ArrayList<>();
+        synchronized (mGlobalLock) {
+            mRoot.forAllWindows(w -> {
+                if (!w.isActivityWindow() && w.isVisibleNow()) {
+                    systemWindows.add(w);
+                }
+            }, false /* traverseTopToBottom */);
+        }
+
+        systemWindows.forEach(w -> {
+            pw.println("---------------------------------");
+            pw.println(w.toString());
+            pw.flush();
+            try (TransferPipe tp = new TransferPipe()) {
+                w.mClient.dumpWindow(tp.getWriteFd());
+                tp.go(fd, timeout);
+            } catch (IOException e) {
+                pw.println("Failure while dumping the window: " + e);
+            } catch (RemoteException e) {
+                pw.println("Got a RemoteException while dumping the window");
+            }
+        });
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 1573d09..99c4736 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -39,6 +39,7 @@
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_PINNED;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_RELATIVE_BOUNDS;
 import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN;
@@ -1627,6 +1628,11 @@
                 }
                 break;
             }
+            case OP_TYPE_SET_PINNED: {
+                final boolean pinned = operation.getBooleanValue();
+                taskFragment.setPinned(pinned);
+                break;
+            }
         }
         return effects;
     }
@@ -1916,7 +1922,6 @@
         final int count = tasksToReparent.size();
         for (int i = 0; i < count; ++i) {
             final Task task = tasksToReparent.get(i);
-            final int prevWindowingMode = task.getWindowingMode();
             if (syncId >= 0) {
                 addToSyncSet(syncId, task);
             }
@@ -1930,12 +1935,6 @@
                         hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
                         false /*moveParents*/, "processChildrenTaskReparentHierarchyOp");
             }
-            // Trim the compatible Recent task (if any) after the Task is reparented and now has
-            // a different windowing mode, in order to prevent redundant Recent tasks after
-            // reparenting.
-            if (prevWindowingMode != task.getWindowingMode()) {
-                mService.mTaskSupervisor.mRecentTasks.removeCompatibleRecentTask(task);
-            }
         }
 
         if (transition != null) transition.collect(newParent);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index c25080f..8fb83fa 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -858,6 +858,10 @@
      * {@link InsetsStateController#notifyInsetsChanged}.
      */
     boolean isReadyToDispatchInsetsState() {
+        if (mStartingData != null) {
+            // Starting window doesn't consider insets.
+            return false;
+        }
         final boolean visible = shouldCheckTokenVisibleRequested()
                 ? isVisibleRequested() : isVisible();
         return visible && mFrozenInsetsState == null;
@@ -1443,14 +1447,17 @@
                     + " last=" + mWindowFrames.mLastFrame + " frame=" + mWindowFrames.mFrame);
         }
 
+        final boolean contentChanged = didFrameInsetsChange || configChanged
+                || dragResizingChanged || attachedFrameChanged;
+        // Cancel unchanged non-sync-buffer redraw request to avoid unnecessary reportResized().
+        if (!contentChanged && !mRedrawForSyncReported && mPrepareSyncSeqId <= 0
+                && mDrawHandlers.isEmpty()) {
+            mRedrawForSyncReported = true;
+        }
+
         // Add a window that is using blastSync to the resizing list if it hasn't been reported
         // already. This because the window is waiting on a finishDrawing from the client.
-        if (didFrameInsetsChange
-                || configChanged
-                || insetsChanged
-                || dragResizingChanged
-                || shouldSendRedrawForSync()
-                || attachedFrameChanged) {
+        if (contentChanged || insetsChanged || shouldSendRedrawForSync()) {
             ProtoLog.v(WM_DEBUG_RESIZE,
                         "Resize reasons for w=%s:  %s configChanged=%b didFrameInsetsChange=%b",
                         this, mWindowFrames.getInsetsChangedInfo(),
@@ -2355,18 +2362,12 @@
         }
 
         final int type = mAttrs.type;
-        if (WindowManagerService.excludeWindowTypeFromTapOutTask(type)) {
-            dc.mTapExcludedWindows.remove(this);
-        }
 
         if (type == TYPE_PRESENTATION || type == TYPE_PRIVATE_PRESENTATION) {
             mWmService.mDisplayManagerInternal.onPresentation(dc.getDisplay().getDisplayId(),
                     /*isShown=*/ false);
         }
 
-        // Remove this window from mTapExcludeProvidingWindows. If it was not registered, this will
-        // not do anything.
-        dc.mTapExcludeProvidingWindows.remove(this);
         dc.getDisplayPolicy().removeWindowLw(this);
 
         disposeInputChannel();
@@ -5522,18 +5523,10 @@
         // Clear the tap excluded region if the region passed in is null or empty.
         if (region == null || region.isEmpty()) {
             mTapExcludeRegion.setEmpty();
-            // Remove this window from mTapExcludeProvidingWindows since it won't be providing
-            // tap exclude regions.
-            currentDisplay.mTapExcludeProvidingWindows.remove(this);
         } else {
             mTapExcludeRegion.set(region);
-            // Make sure that this window is registered as one that provides a tap exclude region
-            // for its containing display.
-            currentDisplay.mTapExcludeProvidingWindows.add(this);
         }
 
-        // Trigger touch exclude region update on current display.
-        currentDisplay.updateTouchExcludeRegion();
         // Trigger touchable region update for this window.
         currentDisplay.getInputMonitor().updateInputWindowsLw(true /* force */);
     }
@@ -6070,6 +6063,10 @@
         return mPrepareSyncSeqId > 0;
     }
 
+    public boolean isActivityWindow() {
+        return mActivityRecord != null;
+    }
+
     void setSecureLocked(boolean isSecure) {
         ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isSecure=%b: %s", isSecure, getName());
         if (secureWindowState()) {
diff --git a/services/core/java/com/android/server/wm/utils/AlwaysTruePredicate.java b/services/core/java/com/android/server/wm/utils/AlwaysTruePredicate.java
new file mode 100644
index 0000000..49dcb6c
--- /dev/null
+++ b/services/core/java/com/android/server/wm/utils/AlwaysTruePredicate.java
@@ -0,0 +1,33 @@
+/*
+ * 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.server.wm.utils;
+
+import java.util.function.Predicate;
+
+/** A simple Predicate to avoid synthetic allocation of lambda expression "o -> true". */
+public class AlwaysTruePredicate implements Predicate<Object> {
+
+    public static final AlwaysTruePredicate INSTANCE = new AlwaysTruePredicate();
+
+    private AlwaysTruePredicate() {
+    }
+
+    @Override
+    public boolean test(Object o) {
+        return true;
+    }
+}
diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp
index e65b903..22c0f73 100644
--- a/services/core/jni/com_android_server_display_DisplayControl.cpp
+++ b/services/core/jni/com_android_server_display_DisplayControl.cpp
@@ -24,9 +24,11 @@
 namespace android {
 
 static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj, jboolean secure,
-                                   jfloat requestedRefreshRate) {
-    ScopedUtfChars name(env, nameObj);
+                                   jstring uniqueIdStr, jfloat requestedRefreshRate) {
+    const ScopedUtfChars name(env, nameObj);
+    const ScopedUtfChars uniqueId(env, uniqueIdStr);
     sp<IBinder> token(SurfaceComposerClient::createDisplay(String8(name.c_str()), bool(secure),
+                                                           std::string(uniqueId.c_str()),
                                                            requestedRefreshRate));
     return javaObjectForIBinder(env, token);
 }
@@ -178,7 +180,7 @@
 
 static const JNINativeMethod sDisplayMethods[] = {
         // clang-format off
-    {"nativeCreateDisplay", "(Ljava/lang/String;ZF)Landroid/os/IBinder;",
+    {"nativeCreateDisplay", "(Ljava/lang/String;ZLjava/lang/String;F)Landroid/os/IBinder;",
             (void*)nativeCreateDisplay },
     {"nativeDestroyDisplay", "(Landroid/os/IBinder;)V",
             (void*)nativeDestroyDisplay },
diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp
index be18835..2307ace 100644
--- a/services/core/jni/com_android_server_hint_HintManagerService.cpp
+++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp
@@ -34,11 +34,8 @@
 
 #include "jni.h"
 
-using aidl::android::hardware::power::SessionConfig;
-using aidl::android::hardware::power::SessionHint;
-using aidl::android::hardware::power::SessionMode;
-using aidl::android::hardware::power::SessionTag;
-using aidl::android::hardware::power::WorkDuration;
+namespace hal = aidl::android::hardware::power;
+
 using android::power::PowerHintSessionWrapper;
 
 namespace android {
@@ -95,10 +92,11 @@
 
 static jlong createHintSessionWithConfig(JNIEnv* env, int32_t tgid, int32_t uid,
                                          std::vector<int32_t> threadIds, int64_t durationNanos,
-                                         int32_t sessionTag, SessionConfig& config) {
+                                         int32_t sessionTag, hal::SessionConfig& config) {
     auto result =
             gPowerHalController.createHintSessionWithConfig(tgid, uid, threadIds, durationNanos,
-                                                            static_cast<SessionTag>(sessionTag),
+                                                            static_cast<hal::SessionTag>(
+                                                                    sessionTag),
                                                             &config);
     if (result.isOk()) {
         jlong session_ptr = reinterpret_cast<jlong>(result.value().get());
@@ -140,12 +138,12 @@
 }
 
 static void reportActualWorkDuration(int64_t session_ptr,
-                                     const std::vector<WorkDuration>& actualDurations) {
+                                     const std::vector<hal::WorkDuration>& actualDurations) {
     auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
     appSession->reportActualWorkDuration(actualDurations);
 }
 
-static void sendHint(int64_t session_ptr, SessionHint hint) {
+static void sendHint(int64_t session_ptr, hal::SessionHint hint) {
     auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
     appSession->sendHint(hint);
 }
@@ -155,7 +153,7 @@
     appSession->setThreads(threadIds);
 }
 
-static void setMode(int64_t session_ptr, SessionMode mode, bool enabled) {
+static void setMode(int64_t session_ptr, hal::SessionMode mode, bool enabled) {
     auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
     appSession->setMode(mode, enabled);
 }
@@ -189,7 +187,7 @@
         return 0;
     }
     std::vector<int32_t> threadIds(tidArray.get(), tidArray.get() + tidArray.size());
-    SessionConfig config;
+    hal::SessionConfig config;
     jlong out = createHintSessionWithConfig(env, tgid, uid, std::move(threadIds), durationNanos,
                                             sessionTag, config);
     if (out <= 0) {
@@ -223,7 +221,7 @@
     ScopedLongArrayRO arrayActualDurations(env, actualDurations);
     ScopedLongArrayRO arrayTimeStamps(env, timeStamps);
 
-    std::vector<WorkDuration> actualList(arrayActualDurations.size());
+    std::vector<hal::WorkDuration> actualList(arrayActualDurations.size());
     for (size_t i = 0; i < arrayActualDurations.size(); i++) {
         actualList[i].timeStampNanos = arrayTimeStamps[i];
         actualList[i].durationNanos = arrayActualDurations[i];
@@ -232,7 +230,7 @@
 }
 
 static void nativeSendHint(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jint hint) {
-    sendHint(session_ptr, static_cast<SessionHint>(hint));
+    sendHint(session_ptr, static_cast<hal::SessionHint>(hint));
 }
 
 static void nativeSetThreads(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jintArray tids) {
@@ -244,13 +242,13 @@
 
 static void nativeSetMode(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jint mode,
                           jboolean enabled) {
-    setMode(session_ptr, static_cast<SessionMode>(mode), enabled);
+    setMode(session_ptr, static_cast<hal::SessionMode>(mode), enabled);
 }
 
 static void nativeReportActualWorkDuration2(JNIEnv* env, jclass /* clazz */, jlong session_ptr,
                                             jobjectArray jWorkDurations) {
     int size = env->GetArrayLength(jWorkDurations);
-    std::vector<WorkDuration> workDurations(size);
+    std::vector<hal::WorkDuration> workDurations(size);
     for (int i = 0; i < size; i++) {
         jobject workDuration = env->GetObjectArrayElement(jWorkDurations, i);
         workDurations[i].workPeriodStartTimestampNanos =
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 32cb251..97f1e19 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -88,7 +88,6 @@
 
 namespace android {
 
-static const bool ENABLE_POINTER_CHOREOGRAPHER = input_flags::enable_pointer_choreographer();
 static const bool ENABLE_INPUT_FILTER_RUST = input_flags::enable_input_filter_rust_impl();
 
 // The exponent used to calculate the pointer speed scaling factor.
@@ -123,12 +122,11 @@
     jmethodID interceptMotionBeforeQueueingNonInteractive;
     jmethodID interceptKeyBeforeDispatching;
     jmethodID dispatchUnhandledKey;
-    jmethodID onPointerDisplayIdChanged;
     jmethodID onPointerDownOutsideFocus;
     jmethodID getVirtualKeyQuietTimeMillis;
     jmethodID getExcludedDeviceNames;
     jmethodID getInputPortAssociations;
-    jmethodID getInputUniqueIdAssociations;
+    jmethodID getInputUniqueIdAssociationsByPort;
     jmethodID getInputUniqueIdAssociationsByDescriptor;
     jmethodID getDeviceTypeAssociations;
     jmethodID getKeyboardLayoutAssociations;
@@ -273,22 +271,23 @@
     void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray);
 
     base::Result<std::unique_ptr<InputChannel>> createInputChannel(const std::string& name);
-    base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId,
+    base::Result<std::unique_ptr<InputChannel>> createInputMonitor(ui::LogicalDisplayId displayId,
                                                                    const std::string& name,
                                                                    gui::Pid pid);
     status_t removeInputChannel(const sp<IBinder>& connectionToken);
     status_t pilferPointers(const sp<IBinder>& token);
 
-    void displayRemoved(JNIEnv* env, int32_t displayId);
-    void setFocusedApplication(JNIEnv* env, int32_t displayId, jobject applicationHandleObj);
-    void setFocusedDisplay(int32_t displayId);
+    void displayRemoved(JNIEnv* env, ui::LogicalDisplayId displayId);
+    void setFocusedApplication(JNIEnv* env, ui::LogicalDisplayId displayId,
+                               jobject applicationHandleObj);
+    void setFocusedDisplay(ui::LogicalDisplayId displayId);
     void setMinTimeBetweenUserActivityPokes(int64_t intervalMillis);
     void setInputDispatchMode(bool enabled, bool frozen);
     void setSystemUiLightsOut(bool lightsOut);
-    void setPointerDisplayId(int32_t displayId);
+    void setPointerDisplayId(ui::LogicalDisplayId displayId);
     int32_t getMousePointerSpeed();
     void setPointerSpeed(int32_t speed);
-    void setMousePointerAccelerationEnabled(int32_t displayId, bool enabled);
+    void setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId, bool enabled);
     void setTouchpadPointerSpeed(int32_t speed);
     void setTouchpadNaturalScrollingEnabled(bool enabled);
     void setTouchpadTapToClickEnabled(bool enabled);
@@ -298,25 +297,22 @@
     void setShowTouches(bool enabled);
     void setInteractive(bool interactive);
     void reloadCalibration();
-    void setPointerIconType(PointerIconStyle iconId);
     void reloadPointerIcons();
     void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled);
-    void setCustomPointerIcon(const SpriteIcon& icon);
     bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
-                        int32_t displayId, DeviceId deviceId, int32_t pointerId,
+                        ui::LogicalDisplayId displayId, DeviceId deviceId, int32_t pointerId,
                         const sp<IBinder>& inputToken);
-    void setPointerIconVisibility(int32_t displayId, bool visible);
+    void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible);
     void setMotionClassifierEnabled(bool enabled);
     std::optional<std::string> getBluetoothAddress(int32_t deviceId);
     void setStylusButtonMotionEventsEnabled(bool enabled);
-    FloatPoint getMouseCursorPosition(int32_t displayId);
+    FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId);
     void setStylusPointerIconEnabled(bool enabled);
     void setInputMethodConnectionIsActive(bool isActive);
 
     /* --- InputReaderPolicyInterface implementation --- */
 
     void getReaderConfiguration(InputReaderConfiguration* outConfig) override;
-    std::shared_ptr<PointerControllerInterface> obtainPointerController(int32_t deviceId) override;
     void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override;
     std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier& identifier,
@@ -329,7 +325,7 @@
     void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override;
     bool isInputMethodConnectionActive() override;
     std::optional<DisplayViewport> getPointerViewportForAssociatedDisplay(
-            int32_t associatedDisplayId) override;
+            ui::LogicalDisplayId associatedDisplayId) override;
 
     /* --- InputDispatcherPolicyInterface implementation --- */
 
@@ -352,13 +348,15 @@
     void notifyVibratorState(int32_t deviceId, bool isOn) override;
     bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override;
     void interceptKeyBeforeQueueing(const KeyEvent& keyEvent, uint32_t& policyFlags) override;
-    void interceptMotionBeforeQueueing(int32_t displayId, uint32_t source, int32_t action,
-                                       nsecs_t when, uint32_t& policyFlags) override;
+    void interceptMotionBeforeQueueing(ui::LogicalDisplayId displayId, uint32_t source,
+                                       int32_t action, nsecs_t when,
+                                       uint32_t& policyFlags) override;
     nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token, const KeyEvent& keyEvent,
                                           uint32_t policyFlags) override;
     std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent& keyEvent,
                                                  uint32_t policyFlags) override;
-    void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override;
+    void pokeUserActivity(nsecs_t eventTime, int32_t eventType,
+                          ui::LogicalDisplayId displayId) override;
     void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
     void setPointerCapture(const PointerCaptureRequest& request) override;
     void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
@@ -367,20 +365,22 @@
 
     /* --- PointerControllerPolicyInterface implementation --- */
 
-    virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId);
-    virtual void loadPointerResources(PointerResources* outResources, int32_t displayId);
+    virtual void loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId);
+    virtual void loadPointerResources(PointerResources* outResources,
+                                      ui::LogicalDisplayId displayId);
     virtual void loadAdditionalMouseResources(
             std::map<PointerIconStyle, SpriteIcon>* outResources,
-            std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t displayId);
+            std::map<PointerIconStyle, PointerAnimation>* outAnimationResources,
+            ui::LogicalDisplayId displayId);
     virtual PointerIconStyle getDefaultPointerIconId();
     virtual PointerIconStyle getDefaultStylusIconId();
     virtual PointerIconStyle getCustomPointerIconId();
-    virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position);
 
     /* --- PointerChoreographerPolicyInterface implementation --- */
     std::shared_ptr<PointerControllerInterface> createPointerController(
             PointerControllerInterface::ControllerType type) override;
-    void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) override;
+    void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId,
+                                       const FloatPoint& position) override;
 
     /* --- InputFilterPolicyInterface implementation --- */
     void notifyStickyModifierStateChanged(uint32_t modifierState,
@@ -404,24 +404,17 @@
         int32_t pointerSpeed{0};
 
         // Displays on which its associated mice will have pointer acceleration disabled.
-        std::set<int32_t> displaysWithMousePointerAccelerationDisabled{};
+        std::set<ui::LogicalDisplayId> displaysWithMousePointerAccelerationDisabled{};
 
         // True if pointer gestures are enabled.
         bool pointerGesturesEnabled{true};
 
-        // Show touches feature enable/disable.
-        bool showTouches{false};
-
         // The latest request to enable or disable Pointer Capture.
         PointerCaptureRequest pointerCaptureRequest{};
 
         // Sprite controller singleton, created on first use.
         std::shared_ptr<SpriteController> spriteController{};
 
-        // TODO(b/293587049): Remove when the PointerChoreographer refactoring is complete.
-        // Pointer controller singleton, created and destroyed as needed.
-        std::weak_ptr<PointerController> legacyPointerController{};
-
         // The list of PointerControllers created and managed by the PointerChoreographer.
         std::list<std::weak_ptr<PointerController>> pointerControllers{};
 
@@ -429,7 +422,7 @@
         std::set<int32_t> disabledInputDevices{};
 
         // Associated Pointer controller display.
-        int32_t pointerDisplayId{ADISPLAY_ID_DEFAULT};
+        ui::LogicalDisplayId pointerDisplayId{ui::LogicalDisplayId::DEFAULT};
 
         // True if stylus button reporting through motion events is enabled.
         bool stylusButtonMotionEventsEnabled{true};
@@ -462,7 +455,7 @@
     void updateInactivityTimeoutLocked();
     void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
     void ensureSpriteControllerLocked();
-    sp<SurfaceControl> getParentSurfaceForPointers(int displayId);
+    sp<SurfaceControl> getParentSurfaceForPointers(ui::LogicalDisplayId displayId);
     static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
     template <typename T>
     std::unordered_map<std::string, T> readMapFromInterleavedJavaArray(
@@ -471,7 +464,7 @@
 
     void forEachPointerControllerLocked(std::function<void(PointerController&)> apply)
             REQUIRES(mLock);
-    PointerIcon loadPointerIcon(JNIEnv* env, int32_t displayId, PointerIconStyle type);
+    PointerIcon loadPointerIcon(JNIEnv* env, ui::LogicalDisplayId displayId, PointerIconStyle type);
 
     static inline JNIEnv* jniEnv() { return AndroidRuntime::getJNIEnv(); }
 };
@@ -502,16 +495,14 @@
                              toString(mLocked.systemUiLightsOut));
         dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed);
         dump += StringPrintf(INDENT "Display with Mouse Pointer Acceleration Disabled: %s\n",
-                             dumpSet(mLocked.displaysWithMousePointerAccelerationDisabled).c_str());
+                             dumpSet(mLocked.displaysWithMousePointerAccelerationDisabled,
+                                     streamableToString)
+                                     .c_str());
         dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
-                toString(mLocked.pointerGesturesEnabled));
-        dump += StringPrintf(INDENT "Show Touches: %s\n", toString(mLocked.showTouches));
+                             toString(mLocked.pointerGesturesEnabled));
         dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n",
                              mLocked.pointerCaptureRequest.isEnable() ? "Enabled" : "Disabled",
                              mLocked.pointerCaptureRequest.seq);
-        if (auto pc = mLocked.legacyPointerController.lock(); pc) {
-            dump += pc->dump();
-        }
     } // release lock
     dump += "\n";
 
@@ -556,9 +547,7 @@
                 [&viewports](PointerController& pc) { pc.onDisplayViewportsUpdated(viewports); });
     } // release lock
 
-    if (ENABLE_POINTER_CHOREOGRAPHER) {
-        mInputManager->getChoreographer().setDisplayViewports(viewports);
-    }
+    mInputManager->getChoreographer().setDisplayViewports(viewports);
     mInputManager->getReader().requestRefreshConfiguration(
             InputReaderConfiguration::Change::DISPLAY_INFO);
 }
@@ -570,7 +559,7 @@
 }
 
 base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputMonitor(
-        int32_t displayId, const std::string& name, gui::Pid pid) {
+        ui::LogicalDisplayId displayId, const std::string& name, gui::Pid pid) {
     ATRACE_CALL();
     return mInputManager->getDispatcher().createInputMonitor(displayId, name, pid);
 }
@@ -634,10 +623,9 @@
         env->DeleteLocalRef(portAssociations);
     }
 
-    outConfig->uniqueIdAssociationsByPort =
-            readMapFromInterleavedJavaArray<std::string>(gServiceClassInfo
-                                                                 .getInputUniqueIdAssociations,
-                                                         "getInputUniqueIdAssociations");
+    outConfig->uniqueIdAssociationsByPort = readMapFromInterleavedJavaArray<
+            std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByPort,
+                         "getInputUniqueIdAssociationsByPort");
 
     outConfig->uniqueIdAssociationsByDescriptor = readMapFromInterleavedJavaArray<
             std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByDescriptor,
@@ -700,8 +688,6 @@
                 : 1;
         outConfig->pointerGesturesEnabled = mLocked.pointerGesturesEnabled;
 
-        outConfig->showTouches = mLocked.showTouches;
-
         outConfig->pointerCaptureRequest = mLocked.pointerCaptureRequest;
 
         outConfig->setDisplayViewports(mLocked.viewports);
@@ -743,10 +729,6 @@
 
 void NativeInputManager::forEachPointerControllerLocked(
         std::function<void(PointerController&)> apply) {
-    if (auto pc = mLocked.legacyPointerController.lock(); pc) {
-        apply(*pc);
-    }
-
     auto it = mLocked.pointerControllers.begin();
     while (it != mLocked.pointerControllers.end()) {
         auto pc = it->lock();
@@ -759,7 +741,7 @@
     }
 }
 
-PointerIcon NativeInputManager::loadPointerIcon(JNIEnv* env, int32_t displayId,
+PointerIcon NativeInputManager::loadPointerIcon(JNIEnv* env, ui::LogicalDisplayId displayId,
                                                 PointerIconStyle type) {
     if (type == PointerIconStyle::TYPE_CUSTOM) {
         LOG(FATAL) << __func__ << ": Cannot load non-system icon type";
@@ -780,51 +762,17 @@
     return android_view_PointerIcon_toNative(env, pointerIconObj.get());
 }
 
-// TODO(b/293587049): Remove the old way of obtaining PointerController when the
-//  PointerChoreographer refactoring is complete.
-std::shared_ptr<PointerControllerInterface> NativeInputManager::obtainPointerController(
-        int32_t /* deviceId */) {
-    ATRACE_CALL();
-    std::scoped_lock _l(mLock);
-
-    std::shared_ptr<PointerController> controller = mLocked.legacyPointerController.lock();
-    if (controller == nullptr) {
-        ensureSpriteControllerLocked();
-
-        // Disable the functionality of the legacy PointerController if PointerChoreographer is
-        // enabled.
-        controller = PointerController::create(this, mLooper, *mLocked.spriteController,
-                                               /*enabled=*/!ENABLE_POINTER_CHOREOGRAPHER);
-        mLocked.legacyPointerController = controller;
-        updateInactivityTimeoutLocked();
-    }
-
-    return controller;
-}
-
 std::shared_ptr<PointerControllerInterface> NativeInputManager::createPointerController(
         PointerControllerInterface::ControllerType type) {
     std::scoped_lock _l(mLock);
     ensureSpriteControllerLocked();
     std::shared_ptr<PointerController> pc =
-            PointerController::create(this, mLooper, *mLocked.spriteController, /*enabled=*/true,
-                                      type);
+            PointerController::create(this, mLooper, *mLocked.spriteController, type);
     mLocked.pointerControllers.emplace_back(pc);
     return pc;
 }
 
-void NativeInputManager::onPointerDisplayIdChanged(int32_t pointerDisplayId,
-                                                   const FloatPoint& position) {
-    if (ENABLE_POINTER_CHOREOGRAPHER) {
-        return;
-    }
-    JNIEnv* env = jniEnv();
-    env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDisplayIdChanged, pointerDisplayId,
-                        position.x, position.y);
-    checkAndClearExceptionFromCallback(env, "onPointerDisplayIdChanged");
-}
-
-void NativeInputManager::notifyPointerDisplayIdChanged(int32_t pointerDisplayId,
+void NativeInputManager::notifyPointerDisplayIdChanged(ui::LogicalDisplayId pointerDisplayId,
                                                        const FloatPoint& position) {
     // Notify the Reader so that devices can be reconfigured.
     { // acquire lock
@@ -833,16 +781,10 @@
             return;
         }
         mLocked.pointerDisplayId = pointerDisplayId;
-        ALOGI("%s: pointer displayId set to: %d", __func__, pointerDisplayId);
+        ALOGI("%s: pointer displayId set to: %s", __func__, pointerDisplayId.toString().c_str());
     } // release lock
     mInputManager->getReader().requestRefreshConfiguration(
             InputReaderConfiguration::Change::DISPLAY_INFO);
-
-    // Notify the system.
-    JNIEnv* env = jniEnv();
-    env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDisplayIdChanged, pointerDisplayId,
-                        position.x, position.y);
-    checkAndClearExceptionFromCallback(env, "onPointerDisplayIdChanged");
 }
 
 void NativeInputManager::notifyStickyModifierStateChanged(uint32_t modifierState,
@@ -853,7 +795,7 @@
     checkAndClearExceptionFromCallback(env, "notifyStickyModifierStateChanged");
 }
 
-sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(int displayId) {
+sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(ui::LogicalDisplayId displayId) {
     JNIEnv* env = jniEnv();
     jlong nativeSurfaceControlPtr =
             env->CallLongMethod(mServiceObj, gServiceClassInfo.getParentSurfaceForPointers,
@@ -875,9 +817,10 @@
         layer = -1;
     }
     mLocked.spriteController =
-            std::make_shared<SpriteController>(mLooper, layer, [this](int displayId) {
-                return getParentSurfaceForPointers(displayId);
-            });
+            std::make_shared<SpriteController>(mLooper, layer,
+                                               [this](ui::LogicalDisplayId displayId) {
+                                                   return getParentSurfaceForPointers(displayId);
+                                               });
     // The SpriteController needs to be shared pointer because the handler callback needs to hold
     // a weak reference so that we can avoid racy conditions when the controller is being destroyed.
     mLocked.spriteController->setHandlerController(mLocked.spriteController);
@@ -1079,8 +1022,7 @@
 
     jobject tokenObj = javaObjectForIBinder(env, token);
     if (tokenObj) {
-        env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyInputChannelBroken,
-                tokenObj);
+        env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyInputChannelBroken, tokenObj);
         checkAndClearExceptionFromCallback(env, "notifyInputChannelBroken");
     }
 }
@@ -1166,12 +1108,12 @@
     checkAndClearExceptionFromCallback(env, "notifyVibratorState");
 }
 
-void NativeInputManager::displayRemoved(JNIEnv* env, int32_t displayId) {
+void NativeInputManager::displayRemoved(JNIEnv* env, ui::LogicalDisplayId displayId) {
     mInputManager->getDispatcher().displayRemoved(displayId);
 }
 
-void NativeInputManager::setFocusedApplication(JNIEnv* env, int32_t displayId,
-        jobject applicationHandleObj) {
+void NativeInputManager::setFocusedApplication(JNIEnv* env, ui::LogicalDisplayId displayId,
+                                               jobject applicationHandleObj) {
     if (!applicationHandleObj) {
         return;
     }
@@ -1181,7 +1123,7 @@
     mInputManager->getDispatcher().setFocusedApplication(displayId, applicationHandle);
 }
 
-void NativeInputManager::setFocusedDisplay(int32_t displayId) {
+void NativeInputManager::setFocusedDisplay(ui::LogicalDisplayId displayId) {
     mInputManager->getDispatcher().setFocusedDisplay(displayId);
 }
 
@@ -1209,24 +1151,8 @@
     });
 }
 
-void NativeInputManager::setPointerDisplayId(int32_t displayId) {
-    if (ENABLE_POINTER_CHOREOGRAPHER) {
-        mInputManager->getChoreographer().setDefaultMouseDisplayId(displayId);
-    } else {
-        { // acquire lock
-            std::scoped_lock _l(mLock);
-
-            if (mLocked.pointerDisplayId == displayId) {
-                return;
-            }
-
-            ALOGI("Setting pointer display id to %d.", displayId);
-            mLocked.pointerDisplayId = displayId;
-        } // release lock
-
-        mInputManager->getReader().requestRefreshConfiguration(
-                InputReaderConfiguration::Change::DISPLAY_INFO);
-    }
+void NativeInputManager::setPointerDisplayId(ui::LogicalDisplayId displayId) {
+    mInputManager->getChoreographer().setDefaultMouseDisplayId(displayId);
 }
 
 int32_t NativeInputManager::getMousePointerSpeed() {
@@ -1250,7 +1176,8 @@
             InputReaderConfiguration::Change::POINTER_SPEED);
 }
 
-void NativeInputManager::setMousePointerAccelerationEnabled(int32_t displayId, bool enabled) {
+void NativeInputManager::setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId,
+                                                            bool enabled) {
     { // acquire lock
         std::scoped_lock _l(mLock);
 
@@ -1260,8 +1187,8 @@
             return;
         }
 
-        ALOGI("Setting mouse pointer acceleration to %s on display %d", toString(enabled),
-              displayId);
+        ALOGI("Setting mouse pointer acceleration to %s on display %s", toString(enabled),
+              displayId.toString().c_str());
         if (enabled) {
             mLocked.displaysWithMousePointerAccelerationDisabled.erase(displayId);
         } else {
@@ -1378,24 +1305,7 @@
 }
 
 void NativeInputManager::setShowTouches(bool enabled) {
-    if (ENABLE_POINTER_CHOREOGRAPHER) {
-        mInputManager->getChoreographer().setShowTouchesEnabled(enabled);
-        return;
-    }
-
-    { // acquire lock
-        std::scoped_lock _l(mLock);
-
-        if (mLocked.showTouches == enabled) {
-            return;
-        }
-
-        ALOGI("Setting show touches feature to %s.", enabled ? "enabled" : "disabled");
-        mLocked.showTouches = enabled;
-    } // release lock
-
-    mInputManager->getReader().requestRefreshConfiguration(
-            InputReaderConfiguration::Change::SHOW_TOUCHES);
+    mInputManager->getChoreographer().setShowTouchesEnabled(enabled);
 }
 
 void NativeInputManager::requestPointerCapture(const sp<IBinder>& windowToken, bool enabled) {
@@ -1411,30 +1321,15 @@
             InputReaderConfiguration::Change::TOUCH_AFFINE_TRANSFORMATION);
 }
 
-void NativeInputManager::setPointerIconType(PointerIconStyle iconId) {
-    std::scoped_lock _l(mLock);
-    std::shared_ptr<PointerController> controller = mLocked.legacyPointerController.lock();
-    if (controller != nullptr) {
-        controller->updatePointerIcon(iconId);
-    }
-}
-
 void NativeInputManager::reloadPointerIcons() {
     std::scoped_lock _l(mLock);
     forEachPointerControllerLocked([](PointerController& pc) { pc.reloadPointerResources(); });
 }
 
-void NativeInputManager::setCustomPointerIcon(const SpriteIcon& icon) {
-    std::scoped_lock _l(mLock);
-    std::shared_ptr<PointerController> controller = mLocked.legacyPointerController.lock();
-    if (controller != nullptr) {
-        controller->setCustomPointerIcon(icon);
-    }
-}
-
 bool NativeInputManager::setPointerIcon(
-        std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, int32_t displayId,
-        DeviceId deviceId, int32_t pointerId, const sp<IBinder>& inputToken) {
+        std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
+        ui::LogicalDisplayId displayId, DeviceId deviceId, int32_t pointerId,
+        const sp<IBinder>& inputToken) {
     if (!mInputManager->getDispatcher().isPointerInWindow(inputToken, displayId, deviceId,
                                                           pointerId)) {
         LOG(WARNING) << "Attempted to change the pointer icon for deviceId " << deviceId
@@ -1446,10 +1341,7 @@
     return mInputManager->getChoreographer().setPointerIcon(std::move(icon), displayId, deviceId);
 }
 
-void NativeInputManager::setPointerIconVisibility(int32_t displayId, bool visible) {
-    if (!ENABLE_POINTER_CHOREOGRAPHER) {
-        return;
-    }
+void NativeInputManager::setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) {
     mInputManager->getChoreographer().setPointerIconVisibility(displayId, visible);
 }
 
@@ -1504,7 +1396,7 @@
 }
 
 std::optional<DisplayViewport> NativeInputManager::getPointerViewportForAssociatedDisplay(
-        int32_t associatedDisplayId) {
+        ui::LogicalDisplayId associatedDisplayId) {
     return mInputManager->getChoreographer().getViewportForPointerDevice(associatedDisplayId);
 }
 
@@ -1579,9 +1471,9 @@
     handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
 }
 
-void NativeInputManager::interceptMotionBeforeQueueing(int32_t displayId, uint32_t source,
-                                                       int32_t action, nsecs_t when,
-                                                       uint32_t& policyFlags) {
+void NativeInputManager::interceptMotionBeforeQueueing(ui::LogicalDisplayId displayId,
+                                                       uint32_t source, int32_t action,
+                                                       nsecs_t when, uint32_t& policyFlags) {
     ATRACE_CALL();
     // Policy:
     // - Ignore untrusted events and pass them along.
@@ -1700,7 +1592,8 @@
     return fallbackEvent;
 }
 
-void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) {
+void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType,
+                                          ui::LogicalDisplayId displayId) {
     ATRACE_CALL();
     android_server_PowerManagerService_userActivity(eventTime, eventType, displayId);
 }
@@ -1731,13 +1624,14 @@
             InputReaderConfiguration::Change::POINTER_CAPTURE);
 }
 
-void NativeInputManager::loadPointerIcon(SpriteIcon* icon, int32_t displayId) {
+void NativeInputManager::loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId) {
     ATRACE_CALL();
     JNIEnv* env = jniEnv();
     *icon = toSpriteIcon(loadPointerIcon(env, displayId, PointerIconStyle::TYPE_ARROW));
 }
 
-void NativeInputManager::loadPointerResources(PointerResources* outResources, int32_t displayId) {
+void NativeInputManager::loadPointerResources(PointerResources* outResources,
+                                              ui::LogicalDisplayId displayId) {
     ATRACE_CALL();
     JNIEnv* env = jniEnv();
 
@@ -1751,7 +1645,8 @@
 
 void NativeInputManager::loadAdditionalMouseResources(
         std::map<PointerIconStyle, SpriteIcon>* outResources,
-        std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t displayId) {
+        std::map<PointerIconStyle, PointerAnimation>* outAnimationResources,
+        ui::LogicalDisplayId displayId) {
     ATRACE_CALL();
     JNIEnv* env = jniEnv();
 
@@ -1818,37 +1713,13 @@
             InputReaderConfiguration::Change::STYLUS_BUTTON_REPORTING);
 }
 
-FloatPoint NativeInputManager::getMouseCursorPosition(int32_t displayId) {
-    if (ENABLE_POINTER_CHOREOGRAPHER) {
-        return mInputManager->getChoreographer().getMouseCursorPosition(displayId);
-    }
-    // To maintain the status-quo, the displayId parameter (used when PointerChoreographer is
-    // enabled) is ignored in the old pipeline.
-    std::scoped_lock _l(mLock);
-    const auto pc = mLocked.legacyPointerController.lock();
-    if (!pc) return {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION};
-
-    return pc->getPosition();
+FloatPoint NativeInputManager::getMouseCursorPosition(ui::LogicalDisplayId displayId) {
+    return mInputManager->getChoreographer().getMouseCursorPosition(displayId);
 }
 
 void NativeInputManager::setStylusPointerIconEnabled(bool enabled) {
-    if (ENABLE_POINTER_CHOREOGRAPHER) {
-        mInputManager->getChoreographer().setStylusPointerIconEnabled(enabled);
-        return;
-    }
-
-    { // acquire lock
-        std::scoped_lock _l(mLock);
-
-        if (mLocked.stylusPointerIconEnabled == enabled) {
-            return;
-        }
-
-        mLocked.stylusPointerIconEnabled = enabled;
-    } // release lock
-
-    mInputManager->getReader().requestRefreshConfiguration(
-            InputReaderConfiguration::Change::DISPLAY_INFO);
+    mInputManager->getChoreographer().setStylusPointerIconEnabled(enabled);
+    return;
 }
 
 void NativeInputManager::setInputMethodConnectionIsActive(bool isActive) {
@@ -2008,7 +1879,7 @@
                                         jstring nameObj, jint pid) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
-    if (displayId == ADISPLAY_ID_NONE) {
+    if (ui::LogicalDisplayId{displayId} == ui::LogicalDisplayId::INVALID) {
         std::string message = "InputChannel used as a monitor must be associated with a display";
         jniThrowRuntimeException(env, message.c_str());
         return nullptr;
@@ -2018,7 +1889,7 @@
     std::string name = nameChars.c_str();
 
     base::Result<std::unique_ptr<InputChannel>> inputChannel =
-            im->createInputMonitor(displayId, name, gui::Pid{pid});
+            im->createInputMonitor(ui::LogicalDisplayId{displayId}, name, gui::Pid{pid});
 
     if (!inputChannel.ok()) {
         std::string message = inputChannel.error().message();
@@ -2065,7 +1936,8 @@
 
     return im->getInputManager()->getDispatcher().setInTouchMode(inTouchMode, gui::Pid{pid},
                                                                  gui::Uid{static_cast<uid_t>(uid)},
-                                                                 hasPermission, displayId);
+                                                                 hasPermission,
+                                                                 ui::LogicalDisplayId{displayId});
 }
 
 static void nativeSetMaximumObscuringOpacityForTouch(JNIEnv* env, jobject nativeImplObj,
@@ -2157,20 +2029,20 @@
 static void nativeDisplayRemoved(JNIEnv* env, jobject nativeImplObj, jint displayId) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
-    im->displayRemoved(env, displayId);
+    im->displayRemoved(env, ui::LogicalDisplayId{displayId});
 }
 
 static void nativeSetFocusedApplication(JNIEnv* env, jobject nativeImplObj, jint displayId,
                                         jobject applicationHandleObj) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
-    im->setFocusedApplication(env, displayId, applicationHandleObj);
+    im->setFocusedApplication(env, ui::LogicalDisplayId{displayId}, applicationHandleObj);
 }
 
 static void nativeSetFocusedDisplay(JNIEnv* env, jobject nativeImplObj, jint displayId) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
-    im->setFocusedDisplay(displayId);
+    im->setFocusedDisplay(ui::LogicalDisplayId{displayId});
 }
 
 static void nativeSetUserActivityPokeInterval(JNIEnv* env, jobject nativeImplObj,
@@ -2226,8 +2098,8 @@
 
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
     if (im->getInputManager()->getDispatcher().transferTouchOnDisplay(destChannelToken,
-                                                                      static_cast<int32_t>(
-                                                                              displayId))) {
+                                                                      ui::LogicalDisplayId{
+                                                                              displayId})) {
         return JNI_TRUE;
     } else {
         return JNI_FALSE;
@@ -2250,7 +2122,7 @@
                                                      jint displayId, jboolean enabled) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
-    im->setMousePointerAccelerationEnabled(displayId, enabled);
+    im->setMousePointerAccelerationEnabled(ui::LogicalDisplayId{displayId}, enabled);
 }
 
 static void nativeSetTouchpadPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) {
@@ -2597,27 +2469,12 @@
     im->setInputDeviceEnabled(deviceId, false);
 }
 
-static void nativeSetPointerIconType(JNIEnv* env, jobject nativeImplObj, jint iconId) {
-    // iconId is set in java from from frameworks/base/core/java/android/view/PointerIcon.java,
-    // where the definition in <input/Input.h> is duplicated as a sealed class (type safe enum
-    // equivalent in Java).
-
-    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
-
-    im->setPointerIconType(static_cast<PointerIconStyle>(iconId));
-}
-
 static void nativeReloadPointerIcons(JNIEnv* env, jobject nativeImplObj) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     im->reloadPointerIcons();
 }
 
-static void nativeSetCustomPointerIcon(JNIEnv* env, jobject nativeImplObj, jobject iconObj) {
-    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
-    im->setCustomPointerIcon(toSpriteIcon(android_view_PointerIcon_toNative(env, iconObj)));
-}
-
 static bool nativeSetPointerIcon(JNIEnv* env, jobject nativeImplObj, jobject iconObj,
                                  jint displayId, jint deviceId, jint pointerId,
                                  jobject inputTokenObj) {
@@ -2635,7 +2492,7 @@
         icon = pointerIcon.style;
     }
 
-    return im->setPointerIcon(std::move(icon), displayId, deviceId, pointerId,
+    return im->setPointerIcon(std::move(icon), ui::LogicalDisplayId{displayId}, deviceId, pointerId,
                               ibinderForJavaObject(env, inputTokenObj));
 }
 
@@ -2643,13 +2500,14 @@
                                            jboolean visible) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
-    im->setPointerIconVisibility(displayId, visible);
+    im->setPointerIconVisibility(ui::LogicalDisplayId{displayId}, visible);
 }
 
 static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jobject nativeImplObj, jint deviceId,
                                            jint displayId) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
-    return im->getInputManager()->getReader().canDispatchToDisplay(deviceId, displayId);
+    return im->getInputManager()->getReader().canDispatchToDisplay(deviceId,
+                                                                   ui::LogicalDisplayId{displayId});
 }
 
 static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jobject nativeImplObj) {
@@ -2661,8 +2519,9 @@
 static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jobject nativeImplObj,
                                                          jint displayId, jboolean isEligible) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
-    im->getInputManager()->getDispatcher().setDisplayEligibilityForPointerCapture(displayId,
-                                                                                  isEligible);
+    im->getInputManager()
+            ->getDispatcher()
+            .setDisplayEligibilityForPointerCapture(ui::LogicalDisplayId{displayId}, isEligible);
 }
 
 static void nativeChangeUniqueIdAssociation(JNIEnv* env, jobject nativeImplObj) {
@@ -2798,7 +2657,7 @@
 
 static void nativeSetPointerDisplayId(JNIEnv* env, jobject nativeImplObj, jint displayId) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
-    im->setPointerDisplayId(displayId);
+    im->setPointerDisplayId(ui::LogicalDisplayId{displayId});
 }
 
 static jstring nativeGetBluetoothAddress(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
@@ -2816,7 +2675,7 @@
 static jfloatArray nativeGetMouseCursorPosition(JNIEnv* env, jobject nativeImplObj,
                                                 jint displayId) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
-    const auto p = im->getMouseCursorPosition(displayId);
+    const auto p = im->getMouseCursorPosition(ui::LogicalDisplayId{displayId});
     const std::array<float, 2> arr = {{p.x, p.y}};
     jfloatArray outArr = env->NewFloatArray(2);
     env->SetFloatArrayRegion(outArr, 0, arr.size(), arr.data());
@@ -2861,6 +2720,11 @@
     im->setInputMethodConnectionIsActive(isActive);
 }
 
+static jint nativeGetLastUsedInputDeviceId(JNIEnv* env, jobject nativeImplObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+    return static_cast<jint>(im->getInputManager()->getReader().getLastUsedInputDeviceId());
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gInputManagerMethods[] = {
@@ -2937,10 +2801,7 @@
         {"isInputDeviceEnabled", "(I)Z", (void*)nativeIsInputDeviceEnabled},
         {"enableInputDevice", "(I)V", (void*)nativeEnableInputDevice},
         {"disableInputDevice", "(I)V", (void*)nativeDisableInputDevice},
-        {"setPointerIconType", "(I)V", (void*)nativeSetPointerIconType},
         {"reloadPointerIcons", "()V", (void*)nativeReloadPointerIcons},
-        {"setCustomPointerIcon", "(Landroid/view/PointerIcon;)V",
-         (void*)nativeSetCustomPointerIcon},
         {"setPointerIcon", "(Landroid/view/PointerIcon;IIILandroid/os/IBinder;)Z",
          (void*)nativeSetPointerIcon},
         {"setPointerIconVisibility", "(IZ)V", (void*)nativeSetPointerIconVisibility},
@@ -2972,6 +2833,7 @@
         {"setAccessibilityStickyKeysEnabled", "(Z)V",
          (void*)nativeSetAccessibilityStickyKeysEnabled},
         {"setInputMethodConnectionIsActive", "(Z)V", (void*)nativeSetInputMethodConnectionIsActive},
+        {"getLastUsedInputDeviceId", "()I", (void*)nativeGetLastUsedInputDeviceId},
 };
 
 #define FIND_CLASS(var, className) \
@@ -3064,9 +2926,6 @@
             "dispatchUnhandledKey",
             "(Landroid/os/IBinder;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;");
 
-    GET_METHOD_ID(gServiceClassInfo.onPointerDisplayIdChanged, clazz, "onPointerDisplayIdChanged",
-                  "(IFF)V");
-
     GET_METHOD_ID(gServiceClassInfo.notifyStickyModifierStateChanged, clazz,
                   "notifyStickyModifierStateChanged", "(II)V");
 
@@ -3082,8 +2941,8 @@
     GET_METHOD_ID(gServiceClassInfo.getInputPortAssociations, clazz,
             "getInputPortAssociations", "()[Ljava/lang/String;");
 
-    GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociations, clazz,
-                  "getInputUniqueIdAssociations", "()[Ljava/lang/String;");
+    GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociationsByPort, clazz,
+                  "getInputUniqueIdAssociationsByPort", "()[Ljava/lang/String;");
 
     GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociationsByDescriptor, clazz,
                   "getInputUniqueIdAssociationsByDescriptor", "()[Ljava/lang/String;");
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index d0b290c..0733968 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -99,7 +99,7 @@
 }
 
 void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType,
-                                                     int32_t displayId) {
+                                                     ui::LogicalDisplayId displayId) {
     if (gPowerManagerServiceObj) {
         // Throttle calls into user activity by event type.
         // We're a little conservative about argument checking here in case the caller
@@ -124,8 +124,8 @@
         JNIEnv* env = AndroidRuntime::getJNIEnv();
 
         env->CallVoidMethod(gPowerManagerServiceObj,
-                gPowerManagerServiceClassInfo.userActivityFromNative,
-                nanoseconds_to_milliseconds(eventTime), eventType, displayId, 0);
+                            gPowerManagerServiceClassInfo.userActivityFromNative,
+                            nanoseconds_to_milliseconds(eventTime), eventType, displayId.val(), 0);
         checkAndClearExceptionFromCallback(env, "userActivityFromNative");
     }
 }
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.h b/services/core/jni/com_android_server_power_PowerManagerService.h
index 36aaceb..ed7fa7c 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.h
+++ b/services/core/jni/com_android_server_power_PowerManagerService.h
@@ -19,6 +19,7 @@
 
 #include <nativehelper/JNIHelp.h>
 #include <powermanager/PowerManager.h>
+#include <ui/LogicalDisplayId.h>
 #include <utils/Timers.h>
 
 #include "jni.h"
@@ -26,7 +27,7 @@
 namespace android {
 
 extern void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType,
-                                                            int32_t displayId);
+                                                            ui::LogicalDisplayId displayId);
 
 } // namespace android
 
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 6143f1d..610b502 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -746,6 +746,20 @@
                     minOccurs="0" maxOccurs="1">
             <xs:annotation name="final"/>
         </xs:element>
+        <!-- list of supported modes for low power. Each point corresponds to one mode.
+          Mode format is : first = refreshRate, second = vsyncRate. E.g. :
+          <lowPowerSupportedModes>
+              <point>
+                  <first>60</first>   // refreshRate
+                  <second>60</second> //vsyncRate
+              </point>
+              ....
+          </lowPowerSupportedModes>
+           -->
+        <xs:element type="nonNegativeFloatToFloatMap" name="lowPowerSupportedModes" minOccurs="0">
+            <xs:annotation name="nullable"/>
+            <xs:annotation name="final"/>
+        </xs:element>
     </xs:complexType>
 
     <xs:complexType name="refreshRateZoneProfiles">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 45ec8f2..203a6d9 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -360,6 +360,7 @@
     method public final java.math.BigInteger getDefaultRefreshRateInHbmHdr();
     method public final java.math.BigInteger getDefaultRefreshRateInHbmSunlight();
     method public final com.android.server.display.config.BlockingZoneConfig getHigherBlockingZoneConfigs();
+    method @Nullable public final com.android.server.display.config.NonNegativeFloatToFloatMap getLowPowerSupportedModes();
     method public final com.android.server.display.config.BlockingZoneConfig getLowerBlockingZoneConfigs();
     method public final com.android.server.display.config.RefreshRateZoneProfiles getRefreshRateZoneProfiles();
     method public final void setDefaultPeakRefreshRate(java.math.BigInteger);
@@ -367,6 +368,7 @@
     method public final void setDefaultRefreshRateInHbmHdr(java.math.BigInteger);
     method public final void setDefaultRefreshRateInHbmSunlight(java.math.BigInteger);
     method public final void setHigherBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig);
+    method public final void setLowPowerSupportedModes(@Nullable com.android.server.display.config.NonNegativeFloatToFloatMap);
     method public final void setLowerBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig);
     method public final void setRefreshRateZoneProfiles(com.android.server.display.config.RefreshRateZoneProfiles);
   }
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 6ef1436..2e126f1 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -66,6 +66,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.credentials.metrics.ApiName;
 import com.android.server.credentials.metrics.ApiStatus;
@@ -294,7 +295,7 @@
         }
     }
 
-    private static Set<ComponentName> getPrimaryProvidersForUserId(Context context, int userId) {
+    static Set<ComponentName> getPrimaryProvidersForUserId(Context context, int userId) {
         final int resolvedUserId = ActivityManager.handleIncomingUser(
                 Binder.getCallingPid(), Binder.getCallingUid(),
                 userId, false, false,
@@ -1166,11 +1167,17 @@
                 settingsWrapper.getStringForUser(
                         Settings.Secure.AUTOFILL_SERVICE, UserHandle.myUserId());
 
-        // If there is an autofill provider and it is the placeholder indicating
+        // If there is an autofill provider and it is the credential autofill service indicating
         // that the currently selected primary provider does not support autofill
-        // then we should wipe the setting to keep it in sync.
-        if (autofillProvider != null && primaryProviders.isEmpty()) {
-            if (autofillProvider.equals(AUTOFILL_PLACEHOLDER_VALUE)) {
+        // then we should keep as is
+        String credentialAutofillService = settingsWrapper.mContext.getResources().getString(
+                R.string.config_defaultCredentialManagerAutofillService);
+        if (autofillProvider != null && primaryProviders.isEmpty() && !TextUtils.equals(
+                autofillProvider, credentialAutofillService)) {
+            // If the existing autofill provider is from the app being removed
+            // then erase the autofill service setting.
+            ComponentName cn = ComponentName.unflattenFromString(autofillProvider);
+            if (cn != null && cn.getPackageName().equals(packageName)) {
                 if (!settingsWrapper.putStringForUser(
                         Settings.Secure.AUTOFILL_SERVICE,
                         "",
@@ -1178,19 +1185,6 @@
                         /* overrideableByRestore= */ true)) {
                     Slog.e(TAG, "Failed to remove autofill package: " + packageName);
                 }
-            } else {
-                // If the existing autofill provider is from the app being removed
-                // then erase the autofill service setting.
-                ComponentName cn = ComponentName.unflattenFromString(autofillProvider);
-                if (cn != null && cn.getPackageName().equals(packageName)) {
-                    if (!settingsWrapper.putStringForUser(
-                            Settings.Secure.AUTOFILL_SERVICE,
-                            "",
-                            UserHandle.myUserId(),
-                            /* overrideableByRestore= */ true)) {
-                        Slog.e(TAG, "Failed to remove autofill package: " + packageName);
-                    }
-                }
             }
         }
 
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
index 38ad5b6..b86db06 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.server.credentials;
 
+import static com.android.server.credentials.CredentialManagerService.getPrimaryProvidersForUserId;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
@@ -30,6 +32,7 @@
 import com.android.server.infra.AbstractPerUserSystemService;
 
 import java.util.List;
+import java.util.Set;
 
 
 /**
@@ -80,9 +83,12 @@
             Slog.i(TAG, "newServiceInfoLocked, mInfo null, "
                     + serviceComponent.flattenToString());
         }
+        Set<ComponentName> primaryProviders =
+                getPrimaryProvidersForUserId(mMaster.getContext(), mUserId);
         mInfo = CredentialProviderInfoFactory.create(
                 getContext(), serviceComponent,
-                mUserId, /*isSystemProvider=*/false);
+                mUserId, /*isSystemProvider=*/false,
+                primaryProviders.contains(serviceComponent));
         return mInfo.getServiceInfo();
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index b1673e2..7a026d5 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -67,6 +67,9 @@
 
     private final ResultReceiver mAutofillCallback;
 
+    @Nullable
+    private ComponentName mPrimaryProviderComponentName = null;
+
     public GetCandidateRequestSession(
             Context context, SessionLifetime sessionCallback,
             Object lock, int userId, int callingUid,
@@ -104,8 +107,12 @@
         if (providerGetCandidateSessions != null) {
             Slog.d(TAG, "In startProviderSession - provider session created and "
                     + "being added for: " + providerInfo.getComponentName());
-            mProviders.put(providerGetCandidateSessions.getComponentName().flattenToString(),
-                    providerGetCandidateSessions);
+            ComponentName componentName = providerGetCandidateSessions
+                    .getComponentName();
+            if (providerInfo.isPrimary()) {
+                mPrimaryProviderComponentName = componentName;
+            }
+            mProviders.put(componentName.flattenToString(), providerGetCandidateSessions);
         }
         return providerGetCandidateSessions;
     }
@@ -138,7 +145,7 @@
 
         try {
             invokeClientCallbackSuccess(new GetCandidateCredentialsResponse(
-                    candidateProviderDataList, intent));
+                    candidateProviderDataList, intent, mPrimaryProviderComponentName));
         } catch (RemoteException e) {
             Slog.e(TAG, "Issue while responding to client with error : " + e);
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
index 950ec77..502607b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.admin.BooleanPolicyValue;
-import android.app.admin.PolicyKey;
 import android.util.Log;
 
 import com.android.modules.utils.TypedXmlPullParser;
@@ -37,8 +36,7 @@
     private static final String TAG = "BooleanPolicySerializer";
 
     @Override
-    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, @NonNull Boolean value)
-            throws IOException {
+    void saveToXml(TypedXmlSerializer serializer, @NonNull Boolean value) throws IOException {
         Objects.requireNonNull(value);
         serializer.attributeBoolean(/* namespace= */ null, ATTR_VALUE, value);
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java
index d24afabe..a65c7e1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java
@@ -18,8 +18,6 @@
 
 import android.annotation.NonNull;
 import android.app.admin.BundlePolicyValue;
-import android.app.admin.PackagePolicyKey;
-import android.app.admin.PolicyKey;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.util.Log;
@@ -53,14 +51,8 @@
     private static final String ATTR_TYPE_BUNDLE_ARRAY = "BA";
 
     @Override
-    void saveToXml(@NonNull PolicyKey policyKey, TypedXmlSerializer serializer,
-            @NonNull Bundle value) throws IOException {
+    void saveToXml(TypedXmlSerializer serializer, @NonNull Bundle value) throws IOException {
         Objects.requireNonNull(value);
-        Objects.requireNonNull(policyKey);
-        if (!(policyKey instanceof PackagePolicyKey)) {
-            throw new IllegalArgumentException("policyKey is not of type "
-                    + "PackagePolicyKey");
-        }
         writeBundle(value, serializer);
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java
index 6303a1a..01f56e0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.admin.ComponentNamePolicyValue;
-import android.app.admin.PolicyKey;
 import android.content.ComponentName;
 import android.util.Log;
 
@@ -37,8 +36,7 @@
     private static final String ATTR_CLASS_NAME = "class-name";
 
     @Override
-    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
-            @NonNull ComponentName value) throws IOException {
+    void saveToXml(TypedXmlSerializer serializer, @NonNull ComponentName value) throws IOException {
         Objects.requireNonNull(value);
         serializer.attribute(
                 /* namespace= */ null, ATTR_PACKAGE_NAME, value.getPackageName());
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index d114337..d733762 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -217,7 +217,7 @@
     <V> void setLocalPolicy(
             @NonNull PolicyDefinition<V> policyDefinition,
             @NonNull EnforcingAdmin enforcingAdmin,
-            @Nullable PolicyValue<V> value,
+            @NonNull PolicyValue<V> value,
             int userId,
             boolean skipEnforcePolicy) {
         Objects.requireNonNull(policyDefinition);
@@ -313,6 +313,7 @@
         }
         updateDeviceAdminServiceOnPolicyAddLocked(enforcingAdmin);
         write();
+        applyToInheritableProfiles(policyDefinition, enforcingAdmin, value, userId);
     }
 
     // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
@@ -400,7 +401,7 @@
      * else remove the policy from child.
      */
     private <V> void applyToInheritableProfiles(PolicyDefinition<V> policyDefinition,
-            EnforcingAdmin enforcingAdmin, PolicyValue<V> value, int userId) {
+            EnforcingAdmin enforcingAdmin, @Nullable PolicyValue<V> value, int userId) {
         if (policyDefinition.isInheritable()) {
             Binder.withCleanCallingIdentity(() -> {
                 List<UserInfo> userInfos = mUserManager.getProfiles(userId);
@@ -1742,14 +1743,17 @@
         }
     }
 
-    <V> void reapplyAllPoliciesLocked() {
+    <V> void reapplyAllPoliciesOnBootLocked() {
         for (PolicyKey policy : mGlobalPolicies.keySet()) {
             PolicyState<?> policyState = mGlobalPolicies.get(policy);
             // Policy definition and value will always be of the same type
             PolicyDefinition<V> policyDefinition =
                     (PolicyDefinition<V>) policyState.getPolicyDefinition();
-            PolicyValue<V> policyValue = (PolicyValue<V>) policyState.getCurrentResolvedPolicy();
-            enforcePolicy(policyDefinition, policyValue, UserHandle.USER_ALL);
+            if (!policyDefinition.shouldSkipEnforcementIfNotChanged()) {
+                PolicyValue<V> policyValue =
+                        (PolicyValue<V>) policyState.getCurrentResolvedPolicy();
+                enforcePolicy(policyDefinition, policyValue, UserHandle.USER_ALL);
+            }
         }
         for (int i = 0; i < mLocalPolicies.size(); i++) {
             int userId = mLocalPolicies.keyAt(i);
@@ -1758,10 +1762,11 @@
                 // Policy definition and value will always be of the same type
                 PolicyDefinition<V> policyDefinition =
                         (PolicyDefinition<V>) policyState.getPolicyDefinition();
-                PolicyValue<V> policyValue =
-                        (PolicyValue<V>) policyState.getCurrentResolvedPolicy();
-                enforcePolicy(policyDefinition, policyValue, userId);
-
+                if (!policyDefinition.shouldSkipEnforcementIfNotChanged()) {
+                    PolicyValue<V> policyValue =
+                            (PolicyValue<V>) policyState.getCurrentResolvedPolicy();
+                    enforcePolicy(policyDefinition, policyValue, userId);
+                }
             }
         }
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f553a5a..85d2a0d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -339,6 +339,7 @@
 import android.app.admin.ManagedSubscriptionsPolicy;
 import android.app.admin.NetworkEvent;
 import android.app.admin.PackagePolicy;
+import android.app.admin.PackageSetPolicyValue;
 import android.app.admin.ParcelableGranteeMap;
 import android.app.admin.ParcelableResource;
 import android.app.admin.PasswordMetrics;
@@ -350,7 +351,6 @@
 import android.app.admin.SecurityLog;
 import android.app.admin.SecurityLog.SecurityEvent;
 import android.app.admin.StartInstallingUpdateCallback;
-import android.app.admin.StringSetPolicyValue;
 import android.app.admin.SystemUpdateInfo;
 import android.app.admin.SystemUpdatePolicy;
 import android.app.admin.UnsafeStateException;
@@ -1985,11 +1985,6 @@
             CryptoTestHelper.runAndLogSelfTest();
         }
 
-        public String[] getPersonalAppsForSuspension(@UserIdInt int userId) {
-            return PersonalAppsSuspensionHelper.forUser(mContext, userId)
-                    .getPersonalAppsForSuspension();
-        }
-
         public long systemCurrentTimeMillis() {
             return System.currentTimeMillis();
         }
@@ -2723,6 +2718,7 @@
                     mDevicePolicyEngine.getResolvedPolicy(
                             PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL));
             setLoggingConfiguration(securityLoggingEnabled, auditLoggingEnabled);
+            mInjector.runCryptoSelfTest();
         } else {
             synchronized (getLockObject()) {
                 mSecurityLogMonitor.start(getSecurityLoggingEnabledUser());
@@ -3355,7 +3351,7 @@
                 break;
             case SystemService.PHASE_SYSTEM_SERVICES_READY:
                 synchronized (getLockObject()) {
-                    mDevicePolicyEngine.reapplyAllPoliciesLocked();
+                    mDevicePolicyEngine.reapplyAllPoliciesOnBootLocked();
                 }
                 break;
             case SystemService.PHASE_ACTIVITY_MANAGER_READY:
@@ -11447,7 +11443,7 @@
             }
             setBackwardsCompatibleAppRestrictions(
                     caller, packageName, restrictions, caller.getUserHandle());
-        } else if (Flags.dmrhCanSetAppRestriction()) {
+        } else if (Flags.dmrhSetAppRestrictions()) {
             final boolean isRoleHolder;
             if (who != null) {
                 // DO or PO
@@ -11488,10 +11484,6 @@
                             new BundlePolicyValue(restrictions),
                             affectedUserId);
                 }
-                Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
-                changeIntent.setPackage(packageName);
-                changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                mContext.sendBroadcastAsUser(changeIntent, UserHandle.of(affectedUserId));
             } else {
                 mInjector.binderWithCleanCallingIdentity(() -> {
                     mUserManager.setApplicationRestrictions(packageName, restrictions,
@@ -12078,7 +12070,7 @@
                 mDevicePolicyEngine.setLocalPolicy(
                         PolicyDefinition.PERMITTED_INPUT_METHODS,
                         admin,
-                        new StringSetPolicyValue(new HashSet<>(packageList)),
+                        new PackageSetPolicyValue(new HashSet<>(packageList)),
                         userId);
             }
         }
@@ -12849,7 +12841,7 @@
                 return Bundle.EMPTY;
             }
             return policies.get(enforcingAdmin).getValue();
-        } else if (Flags.dmrhCanSetAppRestriction()) {
+        } else if (Flags.dmrhSetAppRestrictions()) {
             final boolean isRoleHolder;
             if (who != null) {
                 // Caller is DO or PO. They cannot call this on parent
@@ -15177,10 +15169,8 @@
             if (statusBarService != null) {
                 int flags1 = disabled ? STATUS_BAR_DISABLE_MASK : StatusBarManager.DISABLE_NONE;
                 int flags2 = disabled ? STATUS_BAR_DISABLE2_MASK : StatusBarManager.DISABLE2_NONE;
-                StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(flags1,
-                        flags2);
-                statusBarService.disableForUser(info, mToken, mContext.getPackageName(), userId,
-                        "setStatusBarDisabledInternal");
+                statusBarService.disableForUser(flags1, mToken, mContext.getPackageName(), userId);
+                statusBarService.disable2ForUser(flags2, mToken, mContext.getPackageName(), userId);
                 return true;
             }
         } catch (RemoteException e) {
@@ -15776,8 +15766,13 @@
                             PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
                             userId);
             List<Bundle> restrictions = new ArrayList<>();
-            for (EnforcingAdmin admin : policies.keySet()) {
-                restrictions.add(policies.get(admin).getValue());
+            for (PolicyValue<Bundle> policyValue: policies.values()) {
+                Bundle value = policyValue.getValue();
+                // Probably not necessary since setApplicationRestrictions only sets non-empty
+                // Bundle, but just in case.
+                if (value != null && !value.isEmpty()) {
+                    restrictions.add(value);
+                }
             }
 
             return mInjector.binderWithCleanCallingIdentity(() -> {
@@ -20363,12 +20358,12 @@
             mDevicePolicyEngine.setGlobalPolicy(
                     PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
                     enforcingAdmin,
-                    new StringSetPolicyValue(packages));
+                    new PackageSetPolicyValue(packages));
         } else {
             mDevicePolicyEngine.setLocalPolicy(
                     PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
                     enforcingAdmin,
-                    new StringSetPolicyValue(packages),
+                    new PackageSetPolicyValue(packages),
                     caller.getUserId());
         }
     }
@@ -21610,9 +21605,12 @@
                                 == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
             }
 
-            if (Flags.headlessSingleUserFixes() && mInjector.userManagerIsHeadlessSystemUserMode()
-                    && isSingleUserMode && !mInjector.isChangeEnabled(
-                    PROVISION_SINGLE_USER_MODE, deviceAdmin.getPackageName(), caller.getUserId())) {
+            if (Flags.headlessSingleMinTargetSdk()
+                    && mInjector.userManagerIsHeadlessSystemUserMode()
+                    && isSingleUserMode
+                    && !mInjector.isChangeEnabled(
+                            PROVISION_SINGLE_USER_MODE, deviceAdmin.getPackageName(),
+                    caller.getUserId())) {
                 throw new IllegalStateException("Device admin is not targeting Android V.");
             }
 
@@ -24047,7 +24045,7 @@
                         mDevicePolicyEngine.setLocalPolicy(
                                 PolicyDefinition.PERMITTED_INPUT_METHODS,
                                 enforcingAdmin,
-                                new StringSetPolicyValue(
+                                new PackageSetPolicyValue(
                                         new HashSet<>(admin.permittedInputMethods)),
                                 admin.getUserHandle().getIdentifier());
                     }
@@ -24056,7 +24054,7 @@
                         mDevicePolicyEngine.setLocalPolicy(
                                 PolicyDefinition.PERMITTED_INPUT_METHODS,
                                 enforcingAdmin,
-                                new StringSetPolicyValue(
+                                new PackageSetPolicyValue(
                                         new HashSet<>(admin.getParentActiveAdmin()
                                                 .permittedInputMethods)),
                                 getProfileParentId(admin.getUserHandle().getIdentifier()));
@@ -24112,12 +24110,14 @@
                         mDevicePolicyEngine.setGlobalPolicy(
                                 PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
                                 enforcingAdmin,
-                                new StringSetPolicyValue(new HashSet<>(admin.protectedPackages)));
+                                new PackageSetPolicyValue(
+                                        new HashSet<>(admin.protectedPackages)));
                     } else {
                         mDevicePolicyEngine.setLocalPolicy(
                                 PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
                                 enforcingAdmin,
-                                new StringSetPolicyValue(new HashSet<>(admin.protectedPackages)),
+                                new PackageSetPolicyValue(
+                                        new HashSet<>(admin.protectedPackages)),
                                 admin.getUserHandle().getIdentifier());
                     }
                 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
index 1000bfa..cbd2847 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
@@ -52,7 +52,18 @@
     EnterpriseSpecificIdCalculator(Context context) {
         TelephonyManager telephonyService = context.getSystemService(TelephonyManager.class);
         Preconditions.checkState(telephonyService != null, "Unable to access telephony service");
-        mImei = telephonyService.getImei(0);
+
+        String imei;
+        try {
+            imei = telephonyService.getImei(0);
+        } catch (UnsupportedOperationException doesNotSupportGms) {
+            // Instead of catching the exception, we could check for FEATURE_TELEPHONY_GSM.
+            // However that runs the risk of changing a device's existing ESID if on these devices
+            // telephonyService.getImei() actually returns non-null even when the device does not
+            // declare FEATURE_TELEPHONY_GSM.
+            imei = null;
+        }
+        mImei = imei;
         String meid;
         try {
             meid = telephonyService.getMeid(0);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
index 45a2d2a..ebbf22c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.admin.IntegerPolicyValue;
-import android.app.admin.PolicyKey;
 import android.util.Log;
 
 import com.android.modules.utils.TypedXmlPullParser;
@@ -37,8 +36,7 @@
     private static final String ATTR_VALUE = "value";
 
     @Override
-    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
-            @NonNull Integer value) throws IOException {
+    void saveToXml(TypedXmlSerializer serializer, @NonNull Integer value) throws IOException {
         Objects.requireNonNull(value);
         serializer.attributeInt(/* namespace= */ null, ATTR_VALUE, value);
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java
index 20bd2d7..13412d0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.app.admin.LockTaskPolicy;
-import android.app.admin.PolicyKey;
 import android.util.Log;
 
 import com.android.modules.utils.TypedXmlPullParser;
@@ -39,8 +38,8 @@
     private static final String ATTR_FLAGS = "flags";
 
     @Override
-    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
-            @NonNull LockTaskPolicy value) throws IOException {
+    void saveToXml(TypedXmlSerializer serializer, @NonNull LockTaskPolicy value)
+            throws IOException {
         Objects.requireNonNull(value);
         serializer.attribute(
                 /* namespace= */ null,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java
index 522c4b5..c363e66 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.admin.LongPolicyValue;
-import android.app.admin.PolicyKey;
 import android.util.Log;
 
 import com.android.modules.utils.TypedXmlPullParser;
@@ -37,8 +36,7 @@
     private static final String ATTR_VALUE = "value";
 
     @Override
-    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
-            @NonNull Long value) throws IOException {
+    void saveToXml(TypedXmlSerializer serializer, @NonNull Long value) throws IOException {
         Objects.requireNonNull(value);
         serializer.attributeLong(/* namespace= */ null, ATTR_VALUE, value);
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSetPolicySerializer.java
similarity index 79%
rename from services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java
rename to services/devicepolicy/java/com/android/server/devicepolicy/PackageSetPolicySerializer.java
index 0265453..c4da029 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSetPolicySerializer.java
@@ -18,9 +18,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.admin.PolicyKey;
 import android.app.admin.PolicyValue;
-import android.app.admin.StringSetPolicyValue;
+import android.app.admin.PackageSetPolicyValue;
 import android.util.Log;
 
 import com.android.modules.utils.TypedXmlPullParser;
@@ -31,12 +30,11 @@
 import java.util.Set;
 
 // TODO(scottjonathan): Replace with generic set implementation
-final class StringSetPolicySerializer extends PolicySerializer<Set<String>> {
+final class PackageSetPolicySerializer extends PolicySerializer<Set<String>> {
     private static final String ATTR_VALUES = "strings";
     private static final String ATTR_VALUES_SEPARATOR = ";";
     @Override
-    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
-            @NonNull Set<String> value) throws IOException {
+    void saveToXml(TypedXmlSerializer serializer, @NonNull Set<String> value) throws IOException {
         Objects.requireNonNull(value);
         serializer.attribute(
                 /* namespace= */ null, ATTR_VALUES, String.join(ATTR_VALUES_SEPARATOR, value));
@@ -47,10 +45,10 @@
     PolicyValue<Set<String>> readFromXml(TypedXmlPullParser parser) {
         String valuesStr = parser.getAttributeValue(/* namespace= */ null, ATTR_VALUES);
         if (valuesStr == null) {
-            Log.e(DevicePolicyEngine.TAG, "Error parsing StringSet policy value.");
+            Log.e(DevicePolicyEngine.TAG, "Error parsing PackageSet policy value.");
             return null;
         }
         Set<String> values = Set.of(valuesStr.split(ATTR_VALUES_SEPARATOR));
-        return new StringSetPolicyValue(values);
+        return new PackageSetPolicyValue(values);
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetUnion.java b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSetUnion.java
similarity index 79%
rename from services/devicepolicy/java/com/android/server/devicepolicy/StringSetUnion.java
rename to services/devicepolicy/java/com/android/server/devicepolicy/PackageSetUnion.java
index 5298960..d1e241b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetUnion.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSetUnion.java
@@ -18,14 +18,15 @@
 
 import android.annotation.NonNull;
 import android.app.admin.PolicyValue;
-import android.app.admin.StringSetPolicyValue;
+import android.app.admin.PackageSetPolicyValue;
+import android.app.admin.StringSetUnion;
 
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.Objects;
 import java.util.Set;
 
-final class StringSetUnion extends ResolutionMechanism<Set<String>> {
+final class PackageSetUnion extends ResolutionMechanism<Set<String>> {
 
     @Override
     PolicyValue<Set<String>> resolve(
@@ -38,17 +39,17 @@
         for (PolicyValue<Set<String>> policy : adminPolicies.values()) {
             unionOfPolicies.addAll(policy.getValue());
         }
-        return new StringSetPolicyValue(unionOfPolicies);
+        return new PackageSetPolicyValue(unionOfPolicies);
     }
 
     @Override
-    android.app.admin.StringSetUnion getParcelableResolutionMechanism() {
-        return new android.app.admin.StringSetUnion();
+    StringSetUnion getParcelableResolutionMechanism() {
+        return new StringSetUnion();
     }
 
 
     @Override
     public String toString() {
-        return "SetUnion {}";
+        return "PackageSetUnion {}";
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
index 8cb511e..7483b43 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
@@ -37,7 +37,6 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
-import android.util.Log;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IAccessibilityManager;
 import android.view.inputmethod.InputMethodInfo;
@@ -107,10 +106,6 @@
         for (final String pkg : unsuspendablePackages) {
             result.remove(pkg);
         }
-
-        if (Log.isLoggable(LOG_TAG, Log.INFO)) {
-            Slogf.i(LOG_TAG, "Packages subject to suspension: %s", String.join(",", result));
-        }
         return result.toArray(new String[0]);
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 7a9fa0f..8bec384 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -51,6 +51,7 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 final class PolicyDefinition<V> {
@@ -82,6 +83,10 @@
     // them.
     private static final int POLICY_FLAG_USER_RESTRICTION_POLICY = 1 << 4;
 
+    // Only invoke the policy enforcer callback when the policy value changes, and do not invoke the
+    // callback in other cases such as device reboots.
+    private static final int POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED = 1 << 5;
+
     private static final MostRestrictive<Boolean> FALSE_MORE_RESTRICTIVE = new MostRestrictive<>(
             List.of(new BooleanPolicyValue(false), new BooleanPolicyValue(true)));
 
@@ -162,9 +167,9 @@
             new PolicyDefinition<>(
                     new NoArgsPolicyKey(
                             DevicePolicyIdentifiers.USER_CONTROL_DISABLED_PACKAGES_POLICY),
-                    new StringSetUnion(),
+                    new PackageSetUnion(),
                     PolicyEnforcerCallbacks::setUserControlDisabledPackages,
-                    new StringSetPolicySerializer());
+                    new PackageSetPolicySerializer());
 
     // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
     // actual policy with the correct arguments (i.e. packageName) when reading the policies from
@@ -231,11 +236,11 @@
                     // Don't need to take in a resolution mechanism since its never used, but might
                     // need some refactoring to not always assume a non-null mechanism.
                     new MostRecent<>(),
-                    POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_NON_COEXISTABLE_POLICY,
-                    // Application restrictions are now stored and retrieved from DPMS, so no
-                    // enforcing is required, however DPMS calls into UM to set restrictions for
-                    // backwards compatibility.
-                    (Bundle value, Context context, Integer userId, PolicyKey policyKey) -> true,
+                    // Only invoke the enforcement callback during policy change and not other state
+                    POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE
+                            | POLICY_FLAG_NON_COEXISTABLE_POLICY
+                            | POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED,
+                    PolicyEnforcerCallbacks::setApplicationRestrictions,
                     new BundlePolicySerializer());
 
     /**
@@ -328,7 +333,7 @@
             new MostRecent<>(),
             POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE,
             (Set<String> value, Context context, Integer userId, PolicyKey policyKey) -> true,
-            new StringSetPolicySerializer());
+            new PackageSetPolicySerializer());
 
 
     static PolicyDefinition<Boolean> SCREEN_CAPTURE_DISABLED = new PolicyDefinition<>(
@@ -581,6 +586,10 @@
         return (mPolicyFlags & POLICY_FLAG_USER_RESTRICTION_POLICY) != 0;
     }
 
+    boolean shouldSkipEnforcementIfNotChanged() {
+        return (mPolicyFlags & POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED) != 0;
+    }
+
     @Nullable
     PolicyValue<V> resolvePolicy(LinkedHashMap<EnforcingAdmin, PolicyValue<V>> adminsPolicy) {
         return mResolutionMechanism.resolve(adminsPolicy);
@@ -610,7 +619,7 @@
      * {@link Object#equals} implementation.
      */
     private PolicyDefinition(
-            PolicyKey key,
+            @NonNull  PolicyKey key,
             ResolutionMechanism<V> resolutionMechanism,
             QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
             PolicySerializer<V> policySerializer) {
@@ -622,11 +631,12 @@
      * {@link Object#equals} and {@link Object#hashCode()} implementation.
      */
     private PolicyDefinition(
-            PolicyKey policyKey,
+            @NonNull  PolicyKey policyKey,
             ResolutionMechanism<V> resolutionMechanism,
             int policyFlags,
             QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
             PolicySerializer<V> policySerializer) {
+        Objects.requireNonNull(policyKey);
         mPolicyKey = policyKey;
         mResolutionMechanism = resolutionMechanism;
         mPolicyFlags = policyFlags;
@@ -684,7 +694,7 @@
 
     void savePolicyValueToXml(TypedXmlSerializer serializer, V value)
             throws IOException {
-        mPolicySerializer.saveToXml(mPolicyKey, serializer, value);
+        mPolicySerializer.saveToXml(serializer, value);
     }
 
     @Nullable
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index eeb4976..04d277e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -37,11 +37,13 @@
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -172,6 +174,29 @@
         return true;
     }
 
+
+    /**
+     * Application restrictions are stored and retrieved from DPMS, so no enforcing (aka pushing
+     * it to UMS) is required. Only need to send broadcast to the target user here as we rely on
+     * the inheritable policy propagation logic in PolicyEngine to apply this policy to multiple
+     * profiles. The broadcast should only be sent when an application restriction is set, so we
+     * rely on the POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED flag so DPE only invokes this callback
+     * when the policy is set, and not during system boot or other situations.
+     */
+    static boolean setApplicationRestrictions(Bundle bundle, Context context, Integer userId,
+            PolicyKey policyKey) {
+        Binder.withCleanCallingIdentity(() -> {
+            PackagePolicyKey key = (PackagePolicyKey) policyKey;
+            String packageName = key.getPackageName();
+            Objects.requireNonNull(packageName);
+            Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
+            changeIntent.setPackage(packageName);
+            changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+            context.sendBroadcastAsUser(changeIntent, UserHandle.of(userId));
+        });
+        return true;
+    }
+
     private static class BlockingCallback {
         private final CountDownLatch mLatch = new CountDownLatch(1);
         private final AtomicReference<Boolean> mValue = new AtomicReference<>();
@@ -196,19 +221,27 @@
         Binder.withCleanCallingIdentity(() -> {
             PackageManagerInternal pmi =
                     LocalServices.getService(PackageManagerInternal.class);
+            AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+
             pmi.setOwnerProtectedPackages(userId,
                     packages == null ? null : packages.stream().toList());
             LocalServices.getService(UsageStatsManagerInternal.class)
                     .setAdminProtectedPackages(
                             packages == null ? null : new ArraySet<>(packages), userId);
 
-            if (Flags.disallowUserControlBgUsageFix()) {
-                if (packages == null) {
-                    return;
+            if (packages == null || packages.isEmpty()) {
+                return;
+            }
+
+            for (int user : resolveUsers(userId)) {
+                if (Flags.disallowUserControlBgUsageFix()) {
+                    setBgUsageAppOp(packages, pmi, user, appOpsManager);
                 }
-                final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
-                resolveUsers(userId).forEach(
-                        user -> setBgUsageAppOp(packages, pmi, user, appOpsManager));
+                if (Flags.disallowUserControlStoppedStateFix()) {
+                    for (String packageName : packages) {
+                        pmi.setPackageStoppedState(packageName, false, user);
+                    }
+                }
             }
         });
         return true;
@@ -375,6 +408,7 @@
     private static void suspendPersonalAppsInPackageManager(Context context, int userId) {
         final String[] appsToSuspend = PersonalAppsSuspensionHelper.forUser(context, userId)
                 .getPersonalAppsForSuspension();
+        Slogf.i(LOG_TAG, "Suspending personal apps: %s", String.join(",", appsToSuspend));
         final String[] failedApps = LocalServices.getService(PackageManagerInternal.class)
                 .setPackagesSuspendedByAdmin(userId, appsToSuspend, true);
         if (!ArrayUtils.isEmpty(failedApps)) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
index 5af2fa2..e83b031 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
@@ -17,7 +17,6 @@
 package com.android.server.devicepolicy;
 
 import android.annotation.NonNull;
-import android.app.admin.PolicyKey;
 import android.app.admin.PolicyValue;
 
 import com.android.modules.utils.TypedXmlPullParser;
@@ -26,7 +25,6 @@
 import java.io.IOException;
 
 abstract class PolicySerializer<V> {
-    abstract void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, @NonNull V value)
-            throws IOException;
+    abstract void saveToXml(TypedXmlSerializer serializer, @NonNull V value) throws IOException;
     abstract PolicyValue<V> readFromXml(TypedXmlPullParser parser);
 }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 8755a80..cfe4e17 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -47,6 +47,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.res.Configuration;
 import android.content.res.Resources.Theme;
+import android.crashrecovery.flags.Flags;
 import android.credentials.CredentialManager;
 import android.database.sqlite.SQLiteCompatibilityWalFlags;
 import android.database.sqlite.SQLiteGlobal;
@@ -1195,11 +1196,13 @@
         mSystemServiceManager.startService(RecoverySystemService.Lifecycle.class);
         t.traceEnd();
 
-        // Now that we have the bare essentials of the OS up and running, take
-        // note that we just booted, which might send out a rescue party if
-        // we're stuck in a runtime restart loop.
-        RescueParty.registerHealthObserver(mSystemContext);
-        PackageWatchdog.getInstance(mSystemContext).noteBoot();
+        if (!Flags.recoverabilityDetection()) {
+            // Now that we have the bare essentials of the OS up and running, take
+            // note that we just booted, which might send out a rescue party if
+            // we're stuck in a runtime restart loop.
+            RescueParty.registerHealthObserver(mSystemContext);
+            PackageWatchdog.getInstance(mSystemContext).noteBoot();
+        }
 
         // Manages LEDs and display backlight so we need it to bring up the display.
         t.traceBegin("StartLightsService");
@@ -1469,9 +1472,12 @@
         boolean enableVrService = context.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE);
 
-        // For debugging RescueParty
-        if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_system", false)) {
-            throw new RuntimeException();
+        if (!Flags.recoverabilityDetection()) {
+            // For debugging RescueParty
+            if (Build.IS_DEBUGGABLE
+                    && SystemProperties.getBoolean("debug.crash_system", false)) {
+                throw new RuntimeException();
+            }
         }
 
         try {
@@ -2910,6 +2916,14 @@
         mPackageManagerService.systemReady();
         t.traceEnd();
 
+        if (Flags.recoverabilityDetection()) {
+            // Now that we have the essential services needed for rescue party, initialize
+            // RescuParty. note that we just booted, which might send out a rescue party if
+            // we're stuck in a runtime restart loop.
+            RescueParty.registerHealthObserver(mSystemContext);
+            PackageWatchdog.getInstance(mSystemContext).noteBoot();
+        }
+
         t.traceBegin("MakeDisplayManagerServiceReady");
         try {
             // TODO: use boot phase and communicate this flag some other way
@@ -3313,6 +3327,14 @@
      * are updated outside of OTA; and to avoid breaking dependencies from system into apexes.
      */
     private void startApexServices(@NonNull TimingsTraceAndSlog t) {
+        if (Flags.recoverabilityDetection()) {
+            // For debugging RescueParty
+            if (Build.IS_DEBUGGABLE
+                    && SystemProperties.getBoolean("debug.crash_system", false)) {
+                throw new RuntimeException();
+            }
+        }
+
         t.traceBegin("startApexServices");
         // TODO(b/192880996): get the list from "android" package, once the manifest entries
         // are migrated to system manifest.
diff --git a/services/people/java/com/android/server/people/TEST_MAPPING b/services/people/java/com/android/server/people/TEST_MAPPING
new file mode 100644
index 0000000..55b355c
--- /dev/null
+++ b/services/people/java/com/android/server/people/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+    "presubmit": [
+        {
+            "name": "FrameworksServicesTests",
+            "options": [
+                {
+                    "include-filter": "com.android.server.people.data"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
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 87af841..d307200 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
@@ -63,11 +63,8 @@
 
     private val privilegedPermissionAllowlistViolations = MutableIndexedSet<String>()
 
-    /**
-     * Test-only switch to enforce signature permission allowlist even on debuggable builds.
-     */
-    @Volatile
-    var isSignaturePermissionAllowlistForceEnforced = false
+    /** Test-only switch to enforce signature permission allowlist even on debuggable builds. */
+    @Volatile var isSignaturePermissionAllowlistForceEnforced = false
 
     override val subjectScheme: String
         get() = UidUri.SCHEME
@@ -108,8 +105,8 @@
         val changedPermissionNames = MutableIndexedSet<String>()
         packageNames.forEachIndexed { _, packageName ->
             // The package may still be removed even if it was once notified as installed.
-            val packageState = newState.externalState.packageStates[packageName]
-                ?: return@forEachIndexed
+            val packageState =
+                newState.externalState.packageStates[packageName] ?: return@forEachIndexed
             adoptPermissions(packageState, changedPermissionNames)
             addPermissionGroups(packageState)
             addPermissions(packageState, changedPermissionNames)
@@ -122,14 +119,14 @@
         }
 
         packageNames.forEachIndexed { _, packageName ->
-            val packageState = newState.externalState.packageStates[packageName]
-                ?: return@forEachIndexed
+            val packageState =
+                newState.externalState.packageStates[packageName] ?: return@forEachIndexed
             val installedPackageState = if (isSystemUpdated) packageState else null
             evaluateAllPermissionStatesForPackage(packageState, installedPackageState)
         }
         packageNames.forEachIndexed { _, packageName ->
-            val packageState = newState.externalState.packageStates[packageName]
-                ?: return@forEachIndexed
+            val packageState =
+                newState.externalState.packageStates[packageName] ?: return@forEachIndexed
             newState.externalState.userIds.forEachIndexed { _, userId ->
                 inheritImplicitPermissionStates(packageState.appId, userId)
             }
@@ -1279,7 +1276,23 @@
                     packageName,
                     permissionName
                 )
-            else -> permissionAllowlist.getSignatureAppAllowlistState(packageName, permissionName)
+            else ->
+                permissionAllowlist.getProductSignatureAppAllowlistState(
+                    packageName,
+                    permissionName
+                )
+                    ?: permissionAllowlist.getVendorSignatureAppAllowlistState(
+                        packageName,
+                        permissionName
+                    )
+                    ?: permissionAllowlist.getSystemExtSignatureAppAllowlistState(
+                        packageName,
+                        permissionName
+                    )
+                    ?: permissionAllowlist.getSignatureAppAllowlistState(
+                        packageName,
+                        permissionName
+                    )
         }
     }
 
@@ -1775,6 +1788,7 @@
                 Manifest.permission.READ_MEDIA_AUDIO,
                 Manifest.permission.READ_MEDIA_IMAGES,
                 Manifest.permission.READ_MEDIA_VIDEO,
+                Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED,
             )
 
         private val NEARBY_DEVICES_PERMISSIONS =
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index b155829..6499556 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -2046,8 +2046,20 @@
                     writer.println("Unknown app ID $appId.")
                 }
             }
+        } else if (args[0] == "--package" && args.size == 2) {
+            val packageName = args[1]
+            service.getState {
+                val packageState = state.externalState.packageStates[packageName]
+                if (packageState != null) {
+                    writer.dumpAppIdState(packageState.appId, state, indexedSetOf(packageName))
+                } else {
+                    writer.println("Unknown package $packageName.")
+                }
+            }
         } else {
-            writer.println("Usage: dumpsys permission [--app-id APP_ID]")
+            writer.println(
+                "Usage: dumpsys permissionmgr [--app-id <APP_ID>] [--package <PACKAGE_NAME>]"
+            )
         }
     }
 
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 622e702..54d101a 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -243,12 +243,12 @@
                                 return;
                             }
                             sSelfService.mIProfcollect.process();
-                            jobFinished(params, false);
                         } catch (RemoteException e) {
                             Log.e(LOG_TAG, "Failed to process profiles in background: "
                                     + e.getMessage());
                         }
                     });
+            jobFinished(params, false);
             return true;
         }
 
diff --git a/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java
index 6a82f16..3e87c6f 100644
--- a/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -543,6 +544,18 @@
         return future.get();
     }
 
+    @Test
+    public void onBindingDied_referenceLost_doesNotThrow() {
+        TransportConnection.TransportConnectionMonitor transportConnectionMonitor =
+                new TransportConnection.TransportConnectionMonitor(
+                        mContext, /* transportConnection= */ null);
+        doThrow(new IllegalArgumentException("Service not registered")).when(
+                mContext).unbindService(any());
+
+        // Test no exception is thrown
+        transportConnectionMonitor.onBindingDied(mTransportComponent);
+    }
+
     private ServiceConnection verifyBindServiceAsUserAndCaptureServiceConnection(Context context) {
         ArgumentCaptor<ServiceConnection> connectionCaptor =
                 ArgumentCaptor.forClass(ServiceConnection.class);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 1d225ba..221a991 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -36,9 +36,10 @@
 import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
+import static java.util.Objects.requireNonNull;
+
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -72,7 +73,10 @@
         super.setUp();
         mVisibilityApplier =
                 (DefaultImeVisibilityApplier) mInputMethodManagerService.getVisibilityApplier();
-        mInputMethodManagerService.setAttachedClientForTesting(mock(ClientState.class));
+        synchronized (ImfLock.class) {
+            mInputMethodManagerService.setAttachedClientForTesting(requireNonNull(
+                    mInputMethodManagerService.getClientStateLocked(mMockInputMethodClient)));
+        }
     }
 
     @Test
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodInfoUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodInfoUtilsTest.java
new file mode 100644
index 0000000..50804da
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodInfoUtilsTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.server.inputmethod;
+
+import static com.android.server.inputmethod.TestUtils.TEST_IME_ID1;
+import static com.android.server.inputmethod.TestUtils.TEST_IME_ID2;
+import static com.android.server.inputmethod.TestUtils.createFakeInputMethodInfo;
+import static com.android.server.inputmethod.TestUtils.createFakeSubtypes;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.inputmethod.InputMethodInfo;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+public final class InputMethodInfoUtilsTest {
+
+    @Test
+    public void testMarshalSameObject() {
+        final var imi = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3));
+        final byte[] buf = InputMethodInfoUtils.marshal(imi);
+
+        assertArrayEquals("The same value must be returned when called multiple times",
+                buf, InputMethodInfoUtils.marshal(imi));
+        assertArrayEquals("The same value must be returned when called multiple times",
+                buf, InputMethodInfoUtils.marshal(imi));
+    }
+
+    @Test
+    public void testMarshalDifferentObjects() {
+        final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3));
+        final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(0));
+
+        assertFalse("Different inputs must yield different byte patterns", Arrays.equals(
+                InputMethodInfoUtils.marshal(imi1), InputMethodInfoUtils.marshal(imi2)));
+    }
+
+    @NonNull
+    private static <T> T readTypedObject(byte[] data, @NonNull Parcelable.Creator<T> creator) {
+        Parcel parcel = null;
+        try {
+            parcel = Parcel.obtain();
+            parcel.unmarshall(data, 0, data.length);
+            parcel.setDataPosition(0);
+            return Objects.requireNonNull(parcel.readTypedObject(creator));
+        } finally {
+            if (parcel != null) {
+                parcel.recycle();
+            }
+        }
+    }
+
+    @Test
+    public void testUnmarshalSameObject() {
+        final var imi = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3));
+        final var cloned = readTypedObject(InputMethodInfoUtils.marshal(imi),
+                InputMethodInfo.CREATOR);
+        assertEquals(imi.getPackageName(), cloned.getPackageName());
+        assertEquals(imi.getSubtypeCount(), cloned.getSubtypeCount());
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index cff2265..3b25cb1 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -29,6 +29,7 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -45,6 +46,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.view.InputChannel;
 import android.view.inputmethod.EditorInfo;
 import android.window.ImeOnBackInvokedDispatcher;
 
@@ -53,6 +55,7 @@
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.inputmethod.IInputMethod;
 import com.android.internal.inputmethod.IInputMethodClient;
+import com.android.internal.inputmethod.IInputMethodSession;
 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
 import com.android.internal.inputmethod.IRemoteInputConnection;
 import com.android.internal.inputmethod.InputBindResult;
@@ -104,6 +107,7 @@
     @Mock protected UserManagerInternal mMockUserManagerInternal;
     @Mock protected InputMethodBindingController mMockInputMethodBindingController;
     @Mock protected IInputMethodClient mMockInputMethodClient;
+    @Mock protected IInputMethodSession mMockInputMethodSession;
     @Mock protected IBinder mWindowToken;
     @Mock protected IRemoteInputConnection mMockRemoteInputConnection;
     @Mock protected IRemoteAccessibilityInputConnection mMockRemoteAccessibilityInputConnection;
@@ -123,6 +127,7 @@
     protected IInputMethodInvoker mMockInputMethodInvoker;
     protected InputMethodManagerService mInputMethodManagerService;
     protected ServiceThread mServiceThread;
+    protected ServiceThread mPackageMonitorThread;
     protected boolean mIsLargeScreen;
     private InputManagerGlobal.TestSession mInputManagerGlobalSession;
 
@@ -218,10 +223,17 @@
 
         mServiceThread =
                 new ServiceThread(
-                        "TestServiceThread",
-                        Process.THREAD_PRIORITY_FOREGROUND, /* allowIo */
-                        false);
-        mInputMethodManagerService = new InputMethodManagerService(mContext, mServiceThread,
+                        "immstest1",
+                        Process.THREAD_PRIORITY_FOREGROUND,
+                        true /* allowIo */);
+        mPackageMonitorThread =
+                new ServiceThread(
+                        "immstest2",
+                        Process.THREAD_PRIORITY_FOREGROUND,
+                        true /* allowIo */);
+        mInputMethodManagerService = new InputMethodManagerService(mContext,
+                InputMethodManagerService.shouldEnableExperimentalConcurrentMultiUserMode(mContext),
+                mServiceThread, mPackageMonitorThread,
                 unusedUserId -> mMockInputMethodBindingController);
         spyOn(mInputMethodManagerService);
 
@@ -246,6 +258,7 @@
 
         // Call InputMethodManagerService#addClient() as a preparation to start interacting with it.
         mInputMethodManagerService.addClient(mMockInputMethodClient, mMockRemoteInputConnection, 0);
+        createSessionForClient(mMockInputMethodClient);
     }
 
     @After
@@ -254,6 +267,10 @@
             mInputMethodManagerService.mInputMethodDeviceConfigs.destroy();
         }
 
+        if (mPackageMonitorThread != null) {
+            mPackageMonitorThread.quitSafely();
+        }
+
         if (mServiceThread != null) {
             mServiceThread.quitSafely();
         }
@@ -295,4 +312,13 @@
                 .hideSoftInput(any() /* hideInputToken */, notNull() /* statsToken */,
                         anyInt() /* flags */, any() /* resultReceiver */);
     }
+
+    protected void createSessionForClient(IInputMethodClient client) {
+        synchronized (ImfLock.class) {
+            ClientState cs = mInputMethodManagerService.getClientStateLocked(client);
+            cs.mCurSession = new InputMethodManagerService.SessionState(cs,
+                    mMockInputMethodInvoker, mMockInputMethodSession, mock(
+                    InputChannel.class));
+        }
+    }
 }
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java
new file mode 100644
index 0000000..be70421
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.server.inputmethod;
+
+import static com.android.server.inputmethod.TestUtils.TEST_IME_ID1;
+import static com.android.server.inputmethod.TestUtils.TEST_IME_ID2;
+import static com.android.server.inputmethod.TestUtils.TEST_IME_ID3;
+import static com.android.server.inputmethod.TestUtils.createFakeInputMethodInfo;
+import static com.android.server.inputmethod.TestUtils.createFakeSubtypes;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.util.ArrayMap;
+import android.view.inputmethod.InputMethodInfo;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Test;
+
+public final class InputMethodMapTest {
+
+    @NonNull
+    private static InputMethodMap toMap(InputMethodInfo... list) {
+        final ArrayMap<String, InputMethodInfo> map = new ArrayMap<>();
+        for (var imi : list) {
+            map.put(imi.getId(), imi);
+        }
+        return InputMethodMap.of(map);
+    }
+
+    @Test
+    public void testAreSameSameObject() {
+        final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0));
+        final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3));
+        final var map = toMap(imi1, imi2);
+        assertTrue("Must return true for the same instance",
+                InputMethodMap.areSame(map, map));
+    }
+
+    @Test
+    public void testAreSameEquivalentObject() {
+        final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0));
+        final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3));
+        assertTrue("Must return true for the equivalent instances",
+                InputMethodMap.areSame(toMap(imi1, imi2), toMap(imi1, imi2)));
+
+        assertTrue("Must return true for the equivalent instances",
+                InputMethodMap.areSame(toMap(imi1, imi2), toMap(imi2, imi1)));
+    }
+
+    @Test
+    public void testAreSameDifferentKeys() {
+        final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0));
+        final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3));
+        final var imi3 = createFakeInputMethodInfo(TEST_IME_ID3, createFakeSubtypes(3));
+        assertFalse("Must return false if keys are different",
+                InputMethodMap.areSame(toMap(imi1), toMap(imi1, imi2)));
+        assertFalse("Must return false if keys are different",
+                InputMethodMap.areSame(toMap(imi1, imi2), toMap(imi1)));
+        assertFalse("Must return false if keys are different",
+                InputMethodMap.areSame(toMap(imi1, imi2), toMap(imi1, imi3)));
+    }
+
+    @Test
+    public void testAreSameDifferentValues() {
+        final var imi1_without_subtypes =
+                createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0));
+        final var imi1_with_subtypes =
+                createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3));
+        final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3));
+        assertFalse("Must return false if values are different",
+                InputMethodMap.areSame(toMap(imi1_without_subtypes), toMap(imi1_with_subtypes)));
+        assertFalse("Must return false if values are different",
+                InputMethodMap.areSame(
+                        toMap(imi1_without_subtypes, imi2),
+                        toMap(imi1_with_subtypes, imi2)));
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/TestUtils.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/TestUtils.java
new file mode 100644
index 0000000..c51ff87f
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/TestUtils.java
@@ -0,0 +1,101 @@
+/*
+ * 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.server.inputmethod;
+
+import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+public final class TestUtils {
+    /**
+     * {@link ComponentName} for fake {@link InputMethodInfo}.
+     */
+    @NonNull
+    public static final ComponentName TEST_IME_ID1 = Objects.requireNonNull(
+            ComponentName.unflattenFromString("com.android.test.testime1/.InputMethod"));
+
+    /**
+     * {@link ComponentName} for fake {@link InputMethodInfo}.
+     */
+    @NonNull
+    public static final ComponentName TEST_IME_ID2 = Objects.requireNonNull(
+            ComponentName.unflattenFromString("com.android.test.testime2/.InputMethod"));
+
+    /**
+     * {@link ComponentName} for fake {@link InputMethodInfo}.
+     */
+    @NonNull
+    public static final ComponentName TEST_IME_ID3 = Objects.requireNonNull(
+            ComponentName.unflattenFromString("com.android.test.testime3/.InputMethod"));
+
+    /**
+     * Creates a list of fake {@link InputMethodSubtype} for unit testing for the given number.
+     *
+     * @param count The number of fake {@link InputMethodSubtype} objects
+     * @return The list of fake {@link InputMethodSubtype} objects
+     */
+    @NonNull
+    public static ArrayList<InputMethodSubtype> createFakeSubtypes(int count) {
+        final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>(count);
+        for (int i = 0; i < count; ++i) {
+            subtypes.add(
+                    new InputMethodSubtype.InputMethodSubtypeBuilder()
+                            .setSubtypeId(i + 0x100)
+                            .setLanguageTag("en-US")
+                            .setSubtypeNameOverride("TestSubtype" + i)
+                            .build());
+        }
+        return subtypes;
+    }
+
+    /**
+     * Creates a fake {@link InputMethodInfo} for unit testing.
+     *
+     * @param componentName {@link ComponentName} of the fake {@link InputMethodInfo}
+     * @param subtypes A list of (fake) {@link InputMethodSubtype}
+     * @return a fake {@link InputMethodInfo} object
+     */
+    @NonNull
+    public static InputMethodInfo createFakeInputMethodInfo(
+            @NonNull ComponentName componentName, @NonNull ArrayList<InputMethodSubtype> subtypes) {
+        final ApplicationInfo ai = new ApplicationInfo();
+        ai.packageName = componentName.getPackageName();
+        ai.enabled = true;
+
+        final ServiceInfo si = new ServiceInfo();
+        si.applicationInfo = ai;
+        si.enabled = true;
+        si.packageName = componentName.getPackageName();
+        si.name = componentName.getClassName();
+        si.exported = true;
+        si.nonLocalizedLabel = "Fake Label";
+
+        final ResolveInfo ri = new ResolveInfo();
+        ri.serviceInfo = si;
+
+        return new InputMethodInfo(ri, false /* isAuxIme */, null /* settingsActivity */,
+                subtypes, 0 /* isDefaultResId */, false /* forceDefault */);
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
index 9e11fa2..e545a49 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
@@ -71,7 +71,7 @@
     @Mock
     private Installer mInstaller;
 
-    private Object mInstallLock;
+    private PackageManagerTracedLock mInstallLock;
 
     @Before
     public void setup() {
@@ -79,7 +79,7 @@
         TEST_USER.serialNumber = TEST_USER_SERIAL;
         Context ctx = InstrumentationRegistry.getContext();
         FileUtils.deleteContents(ctx.getCacheDir());
-        mInstallLock = new Object();
+        mInstallLock = new PackageManagerTracedLock();
         MockitoAnnotations.initMocks(this);
         mUserDataPreparer = new TestUserDataPreparer(mInstaller, mInstallLock, mContextMock,
                 ctx.getCacheDir());
@@ -238,8 +238,8 @@
     private static class TestUserDataPreparer extends UserDataPreparer {
         File testDir;
 
-        TestUserDataPreparer(Installer installer, Object installLock, Context context,
-                File testDir) {
+        TestUserDataPreparer(Installer installer, PackageManagerTracedLock installLock,
+                Context context, File testDir) {
             super(installer, installLock, context);
             this.testDir = testDir;
         }
diff --git a/services/tests/apexsystemservices/OWNERS b/services/tests/apexsystemservices/OWNERS
index 0295b9e..8b6675a 100644
--- a/services/tests/apexsystemservices/OWNERS
+++ b/services/tests/apexsystemservices/OWNERS
@@ -1,4 +1 @@
-omakoto@google.com
-satayev@google.com
-
 include platform/packages/modules/common:/OWNERS
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index a0a611f..46d08b0 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -21,7 +21,6 @@
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
 import static com.android.server.display.config.SensorData.TEMPERATURE_TYPE_SKIN;
-import static com.android.server.display.config.SensorData.SupportedMode;
 import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat;
 import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat;
 
@@ -58,6 +57,7 @@
 import com.android.server.display.config.HysteresisLevels;
 import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint;
 import com.android.server.display.config.RefreshRateData;
+import com.android.server.display.config.SupportedModeData;
 import com.android.server.display.config.ThermalStatus;
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.feature.flags.Flags;
@@ -613,7 +613,7 @@
         assertEquals(mDisplayDeviceConfig.getProximitySensor().minRefreshRate, 60, SMALL_DELTA);
         assertEquals(mDisplayDeviceConfig.getProximitySensor().maxRefreshRate, 90, SMALL_DELTA);
         assertThat(mDisplayDeviceConfig.getProximitySensor().supportedModes).hasSize(2);
-        SupportedMode mode = mDisplayDeviceConfig.getProximitySensor().supportedModes.get(0);
+        SupportedModeData mode = mDisplayDeviceConfig.getProximitySensor().supportedModes.get(0);
         assertEquals(mode.refreshRate, 60, SMALL_DELTA);
         assertEquals(mode.vsyncRate, 65, SMALL_DELTA);
         mode = mDisplayDeviceConfig.getProximitySensor().supportedModes.get(1);
@@ -933,6 +933,21 @@
         assertEquals(0.2f, mDisplayDeviceConfig.getNitsFromBacklight(0.0f), ZERO_DELTA);
     }
 
+    @Test
+    public void testLowPowerSupportedModesFromConfigFile() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile();
+
+        RefreshRateData refreshRateData = mDisplayDeviceConfig.getRefreshRateData();
+        assertNotNull(refreshRateData);
+        assertThat(refreshRateData.lowPowerSupportedModes).hasSize(2);
+        SupportedModeData supportedModeData = refreshRateData.lowPowerSupportedModes.get(0);
+        assertThat(supportedModeData.refreshRate).isEqualTo(60);
+        assertThat(supportedModeData.vsyncRate).isEqualTo(60);
+        supportedModeData = refreshRateData.lowPowerSupportedModes.get(1);
+        assertThat(supportedModeData.refreshRate).isEqualTo(60);
+        assertThat(supportedModeData.vsyncRate).isEqualTo(120);
+    }
+
     private String getValidLuxThrottling() {
         return "<luxThrottling>\n"
                 + "    <brightnessLimitMap>\n"
@@ -1089,6 +1104,19 @@
                 + "</proxSensor>\n";
     }
 
+    private String getLowPowerConfig() {
+        return "<lowPowerSupportedModes>\n"
+                + "    <point>\n"
+                + "        <first>60</first>\n"
+                + "        <second>60</second>\n"
+                + "    </point>\n"
+                + "    <point>\n"
+                + "        <first>60</first>\n"
+                + "        <second>120</second>\n"
+                + "    </point>\n"
+                + "</lowPowerSupportedModes>\n";
+    }
+
     private String getHdrBrightnessConfig() {
         return "<hdrBrightnessConfig>\n"
               + "    <brightnessMap>\n"
@@ -1620,6 +1648,7 @@
                 +               "</displayBrightnessPoint>\n"
                 +           "</blockingZoneThreshold>\n"
                 +       "</higherBlockingZoneConfigs>\n"
+                + getLowPowerConfig()
                 +   "</refreshRate>\n"
                 +   "<screenOffBrightnessSensorValueToLux>\n"
                 +       "<item>-1</item>\n"
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 1666fef..54b2d4d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -282,7 +282,7 @@
             return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
                     new VirtualDisplayAdapter.SurfaceControlDisplayFactory() {
                         @Override
-                        public IBinder createDisplay(String name, boolean secure,
+                        public IBinder createDisplay(String name, boolean secure, String uniqueId,
                                 float requestedRefreshRate) {
                             return mMockDisplayToken;
                         }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 80f38eb..e5685c7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1790,6 +1790,7 @@
         verify(mHolder.animator).animateTo(eq(brightness),
                 /* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(),
                 /* ignoreAnimationLimits= */ anyBoolean());
+        verify(mHolder.brightnessSetting).setBrightness(brightness);
     }
 
     @Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index ae6361b..df96712 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -46,6 +46,7 @@
 import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
 import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
 import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
+import com.android.server.display.brightness.strategy.FallbackBrightnessStrategy;
 import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy;
 import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy;
 import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy;
@@ -90,6 +91,8 @@
     @Mock
     private AutoBrightnessFallbackStrategy mAutoBrightnessFallbackStrategy;
     @Mock
+    private FallbackBrightnessStrategy mFallbackBrightnessStrategy;
+    @Mock
     private Resources mResources;
     @Mock
     private DisplayManagerFlags mDisplayManagerFlags;
@@ -135,7 +138,7 @@
 
                 @Override
                 AutomaticBrightnessStrategy getAutomaticBrightnessStrategy1(Context context,
-                        int displayId) {
+                        int displayId, DisplayManagerFlags displayManagerFlags) {
                     return mAutomaticBrightnessStrategy;
                 }
 
@@ -155,6 +158,11 @@
                 AutoBrightnessFallbackStrategy getAutoBrightnessFallbackStrategy() {
                     return mAutoBrightnessFallbackStrategy;
                 }
+
+                @Override
+                FallbackBrightnessStrategy getFallbackBrightnessStrategy() {
+                    return mFallbackBrightnessStrategy;
+                }
             };
 
     @Rule
@@ -355,6 +363,25 @@
     }
 
     @Test
+    public void selectStrategy_selectsFallbackStrategyAsAnUltimateFallback() {
+        when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true);
+        mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
+                mInjector, DISPLAY_ID, mDisplayManagerFlags);
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        displayPowerRequest.screenBrightnessOverride = Float.NaN;
+        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
+        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+        when(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()).thenReturn(false);
+        when(mAutomaticBrightnessStrategy.isAutoBrightnessValid()).thenReturn(false);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(
+                        new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON,
+                                0.1f, false)),
+                mFallbackBrightnessStrategy);
+    }
+
+    @Test
     public void selectStrategyCallsPostProcessorForAllStrategies() {
         when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true);
         mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index 3e78118..19bff56 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -18,8 +18,10 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -45,6 +47,7 @@
 import com.android.server.display.brightness.BrightnessEvent;
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.brightness.StrategyExecutionRequest;
+import com.android.server.display.feature.DisplayManagerFlags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -64,6 +67,9 @@
     @Mock
     private AutomaticBrightnessController mAutomaticBrightnessController;
 
+    @Mock
+    private DisplayManagerFlags mDisplayManagerFlags;
+
     private BrightnessConfiguration mBrightnessConfiguration;
     private float mDefaultScreenAutoBrightnessAdjustment;
     private Context mContext;
@@ -80,7 +86,8 @@
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, Float.NaN);
         Settings.System.putFloat(mContext.getContentResolver(),
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.5f);
-        mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, DISPLAY_ID);
+        mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, DISPLAY_ID,
+                mDisplayManagerFlags);
 
         mBrightnessConfiguration = new BrightnessConfiguration.Builder(
                 new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build();
@@ -247,6 +254,46 @@
     }
 
     @Test
+    public void testAutoBrightnessState_modeSwitch() {
+        // Setup the test
+        when(mDisplayManagerFlags.areAutoBrightnessModesEnabled()).thenReturn(true);
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        boolean allowAutoBrightnessWhileDozing = false;
+        int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+        float lastUserSetBrightness = 0.2f;
+        boolean userSetBrightnessChanged = true;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        float pendingBrightnessAdjustment = 0.1f;
+        Settings.System.putFloat(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
+
+        // Validate no interaction when automaticBrightnessController is in idle mode
+        when(mAutomaticBrightnessController.isInIdleMode()).thenReturn(true);
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController, never()).switchMode(anyInt());
+
+        // Validate interaction when automaticBrightnessController is in non-idle mode, and display
+        // state is ON
+        when(mAutomaticBrightnessController.isInIdleMode()).thenReturn(false);
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController).switchMode(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT);
+
+        // Validate interaction when automaticBrightnessController is in non-idle mode, and display
+        // state is DOZE
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_DOZE,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController).switchMode(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE);
+    }
+
+    @Test
     public void accommodateUserBrightnessChangesWorksAsExpected() {
         // Verify the state if automaticBrightnessController is configured.
         assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
@@ -390,7 +437,8 @@
     @Test
     public void testVerifyNoAutoBrightnessAdjustmentsArePopulatedForNonDefaultDisplay() {
         int newDisplayId = 1;
-        mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, newDisplayId);
+        mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, newDisplayId,
+                mDisplayManagerFlags);
         mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(0.3f);
         assertEquals(0.5f, mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(),
                 0.0f);
@@ -429,8 +477,7 @@
             updateBrightness_constructsDisplayBrightnessState_withAdjustmentAutoAdjustmentFlag() {
         BrightnessEvent brightnessEvent = new BrightnessEvent(DISPLAY_ID);
         mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(
-                mContext, DISPLAY_ID, displayId -> brightnessEvent);
-        new AutomaticBrightnessStrategy(mContext, DISPLAY_ID);
+                mContext, DISPLAY_ID, displayId -> brightnessEvent, mDisplayManagerFlags);
         mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
                 mAutomaticBrightnessController);
         float brightness = 0.4f;
@@ -461,8 +508,7 @@
             updateBrightness_constructsDisplayBrightnessState_withAdjustmentTempAdjustmentFlag() {
         BrightnessEvent brightnessEvent = new BrightnessEvent(DISPLAY_ID);
         mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(
-                mContext, DISPLAY_ID, displayId -> brightnessEvent);
-        new AutomaticBrightnessStrategy(mContext, DISPLAY_ID);
+                mContext, DISPLAY_ID, displayId -> brightnessEvent, mDisplayManagerFlags);
         mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
                 mAutomaticBrightnessController);
         float brightness = 0.4f;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java
new file mode 100644
index 0000000..c4767ae
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.server.display.brightness.strategy;
+
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.display.DisplayManagerInternal;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.StrategyExecutionRequest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+
+public class FallbackBrightnessStrategyTest {
+    private FallbackBrightnessStrategy mFallbackBrightnessStrategy;
+
+    @Before
+    public void before() {
+        mFallbackBrightnessStrategy = new FallbackBrightnessStrategy();
+    }
+
+    @Test
+    public void updateBrightness_currentBrightnessIsSet() {
+        DisplayManagerInternal.DisplayPowerRequest
+                displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest();
+        float currentBrightness = 0.2f;
+        BrightnessReason brightnessReason = new BrightnessReason();
+        brightnessReason.setReason(BrightnessReason.REASON_MANUAL);
+        DisplayBrightnessState expectedDisplayBrightnessState =
+                new DisplayBrightnessState.Builder()
+                        .setBrightness(currentBrightness)
+                        .setBrightnessReason(brightnessReason)
+                        .setSdrBrightness(currentBrightness)
+                        .setDisplayBrightnessStrategyName(mFallbackBrightnessStrategy.getName())
+                        .setShouldUpdateScreenBrightnessSetting(true)
+                        .build();
+        DisplayBrightnessState updatedDisplayBrightnessState =
+                mFallbackBrightnessStrategy.updateBrightness(
+                        new StrategyExecutionRequest(displayPowerRequest, currentBrightness));
+        assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
index a785300..27f87aa 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
@@ -161,4 +161,20 @@
                 .isEqualTo(Integer.toString(testPropertyValue));
     }
 
+    @Test
+    public void daltonizer_defaultValues() {
+        synchronized (mDtm.mDaltonizerModeLock) {
+            assertThat(mDtm.mDaltonizerMode).isEqualTo(-1);
+            assertThat(mDtm.mDaltonizerLevel).isEqualTo(-1);
+        }
+    }
+
+    @Test
+    public void setDaltonizerMode_newValues_valuesUpdated() {
+        mDtm.setDaltonizerMode(0, 0);
+        synchronized (mDtm.mDaltonizerModeLock) {
+            assertThat(mDtm.mDaltonizerMode).isEqualTo(0);
+            assertThat(mDtm.mDaltonizerLevel).isEqualTo(0);
+        }
+    }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index cd1e9e8..714b423 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -131,7 +131,8 @@
             /* defaultRefreshRate= */ 0,
             /* defaultPeakRefreshRate= */ 0,
             /* defaultRefreshRateInHbmHdr= */ 0,
-            /* defaultRefreshRateInHbmSunlight= */ 0);
+            /* defaultRefreshRateInHbmSunlight= */ 0,
+            /* lowPowerSupportedModes =*/ List.of());
 
     public static Collection<Object[]> getAppRequestedSizeTestCases() {
         var appRequestedSizeTestCases = Arrays.asList(new Object[][] {
@@ -157,7 +158,7 @@
                                         APP_MODE_HIGH_90.getPhysicalHeight()),
                                 Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
                                 Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()),
-                                Vote.PRIORITY_LOW_POWER_MODE,
+                                Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE,
                                 Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(),
                                         LIMIT_MODE_70.getPhysicalHeight()))},
                 {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(),
@@ -169,7 +170,7 @@
                                         APP_MODE_65.getPhysicalHeight()),
                                 Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
                                 Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()),
-                                Vote.PRIORITY_LOW_POWER_MODE,
+                                Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE,
                                 Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(),
                                         LIMIT_MODE_70.getPhysicalHeight()))},
                 {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(),
@@ -181,7 +182,7 @@
                                         APP_MODE_65.getPhysicalHeight()),
                                 Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
                                 Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()),
-                                Vote.PRIORITY_LOW_POWER_MODE,
+                                Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE,
                                 Vote.forSizeAndPhysicalRefreshRatesRange(
                                     0, 0,
                                     LIMIT_MODE_70.getPhysicalWidth(),
@@ -197,7 +198,7 @@
                                         APP_MODE_65.getPhysicalHeight()),
                                 Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
                                 Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()),
-                                Vote.PRIORITY_LOW_POWER_MODE,
+                                Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE,
                                 Vote.forSizeAndPhysicalRefreshRatesRange(
                                     0, 0,
                                     LIMIT_MODE_70.getPhysicalWidth(),
@@ -213,7 +214,7 @@
                                         APP_MODE_HIGH_90.getPhysicalHeight()),
                                 Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
                                 Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()),
-                                Vote.PRIORITY_LOW_POWER_MODE,
+                                Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE,
                                 Vote.forSizeAndPhysicalRefreshRatesRange(
                                     0, 0,
                                     LIMIT_MODE_70.getPhysicalWidth(),
@@ -229,7 +230,7 @@
                                         APP_MODE_HIGH_90.getPhysicalHeight()),
                                 Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
                                 Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()),
-                                Vote.PRIORITY_LOW_POWER_MODE,
+                                Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE,
                                 Vote.forSizeAndPhysicalRefreshRatesRange(
                                     0, 0,
                                     LIMIT_MODE_70.getPhysicalWidth(),
@@ -245,7 +246,7 @@
                                 Vote.PRIORITY_APP_REQUEST_SIZE,
                                 Vote.forSize(APP_MODE_65.getPhysicalWidth(),
                                         APP_MODE_65.getPhysicalHeight()),
-                                Vote.PRIORITY_LOW_POWER_MODE,
+                                Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE,
                                 Vote.forPhysicalRefreshRates(
                                         0, 64.99f))}});
 
@@ -598,7 +599,7 @@
                 < Vote.PRIORITY_APP_REQUEST_SIZE);
 
         assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH
-                > Vote.PRIORITY_LOW_POWER_MODE);
+                > Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE);
 
         Display.Mode[] modes = new Display.Mode[4];
         modes[0] = new Display.Mode(
@@ -676,9 +677,9 @@
 
     @Test
     public void testLPMHasHigherPriorityThanUser() {
-        assertTrue(Vote.PRIORITY_LOW_POWER_MODE
+        assertTrue(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE
                 > Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
-        assertTrue(Vote.PRIORITY_LOW_POWER_MODE
+        assertTrue(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE
                 > Vote.PRIORITY_APP_REQUEST_SIZE);
 
         Display.Mode[] modes = new Display.Mode[4];
@@ -700,7 +701,7 @@
                 Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
         votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
                 appRequestedMode.getPhysicalHeight()));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(60, 60));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(60, 60));
         director.injectVotesByDisplay(votesByDisplay);
         DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.baseModeId).isEqualTo(2);
@@ -715,7 +716,7 @@
                 Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
         votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
                 appRequestedMode.getPhysicalHeight()));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(90, 90));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(90, 90));
         director.injectVotesByDisplay(votesByDisplay);
         desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.baseModeId).isEqualTo(4);
@@ -730,7 +731,7 @@
                 Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
         votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
                 appRequestedMode.getPhysicalHeight()));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(60, 60));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(60, 60));
         director.injectVotesByDisplay(votesByDisplay);
         desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.baseModeId).isEqualTo(2);
@@ -745,7 +746,7 @@
                 Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
         votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
                 appRequestedMode.getPhysicalHeight()));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(90, 90));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(90, 90));
         director.injectVotesByDisplay(votesByDisplay);
         desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.baseModeId).isEqualTo(4);
@@ -906,7 +907,7 @@
                 Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
         votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
                 Vote.forRenderFrameRates(60, 60));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60));
         director.injectVotesByDisplay(votesByDisplay);
 
         assertThat(director.shouldAlwaysRespectAppRequestedMode()).isFalse();
@@ -946,7 +947,7 @@
         votesByDisplay.put(DISPLAY_ID, votes);
         votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
                 Vote.forRenderFrameRates(30, 90));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60));
 
         director.injectVotesByDisplay(votesByDisplay);
         assertThat(director.getModeSwitchingType())
@@ -987,7 +988,7 @@
         votesByDisplay.put(DISPLAY_ID, votes);
         votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
                 Vote.forRenderFrameRates(30, 90));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60));
 
         director.injectVotesByDisplay(votesByDisplay);
         assertThat(director.getModeSwitchingType())
@@ -1029,7 +1030,7 @@
         votesByDisplay.put(DISPLAY_ID, votes);
         votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
                 Vote.forRenderFrameRates(30, 90));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60));
 
         director.injectVotesByDisplay(votesByDisplay);
         assertThat(director.getModeSwitchingType())
@@ -1900,7 +1901,7 @@
         director.start(createMockSensorManager());
 
         SparseArray<Vote> votes = new SparseArray<>();
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 50f));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 50f));
 
         SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
         votesByDisplay.put(DISPLAY_ID_2, votes);
@@ -2298,7 +2299,7 @@
         votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
                 Vote.forRenderFrameRates(90, Float.POSITIVE_INFINITY));
         votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60));
         director.injectVotesByDisplay(votesByDisplay);
         DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(50);
@@ -2311,7 +2312,7 @@
         votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
                 Vote.forRenderFrameRates(80, Float.POSITIVE_INFINITY));
         votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 90));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 90));
         desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(80);
         assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(80);
@@ -2323,7 +2324,7 @@
         votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
                 Vote.forRenderFrameRates(80, Float.POSITIVE_INFINITY));
         votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 90));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 90));
         desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
         assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
@@ -2343,7 +2344,7 @@
         votesByDisplay.put(DISPLAY_ID, votes);
         votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
                 Vote.forBaseModeRefreshRate(70));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60));
         director.injectVotesByDisplay(votesByDisplay);
         DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
@@ -2360,7 +2361,7 @@
         votes.clear();
         votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
                 Vote.forBaseModeRefreshRate(55));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60));
         director.injectVotesByDisplay(votesByDisplay);
         desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
@@ -2374,7 +2375,7 @@
                 Vote.forRenderFrameRates(0, 52));
         votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
                 Vote.forBaseModeRefreshRate(55));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60));
         director.injectVotesByDisplay(votesByDisplay);
         desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
@@ -2392,7 +2393,7 @@
                 Vote.forRenderFrameRates(0, 58));
         votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
                 Vote.forBaseModeRefreshRate(55));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60));
         director.injectVotesByDisplay(votesByDisplay);
         desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
@@ -2521,7 +2522,7 @@
 
 
         votes.put(Vote.PRIORITY_UDFPS, Vote.forPhysicalRefreshRates(120, 120));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(90, 90));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(90, 90));
         director.injectVotesByDisplay(votesByDisplay);
         DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
         assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(120);
@@ -2542,7 +2543,7 @@
         SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
         votesByDisplay.put(DISPLAY_ID, votes);
 
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60));
         votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
                 Vote.forRenderFrameRates(0, 30));
         director.injectVotesByDisplay(votesByDisplay);
@@ -3168,7 +3169,8 @@
                 /* defaultRefreshRate= */ 60,
                 /* defaultPeakRefreshRate= */ 65,
                 /* defaultRefreshRateInHbmHdr= */ 65,
-                /* defaultRefreshRateInHbmSunlight= */ 75);
+                /* defaultRefreshRateInHbmSunlight= */ 75,
+                /* lowPowerSupportedModes= */ List.of());
         when(displayDeviceConfig.getRefreshRateData()).thenReturn(refreshRateData);
         when(displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate()).thenReturn(50);
         when(displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate()).thenReturn(55);
@@ -3390,9 +3392,10 @@
 
         ArgumentCaptor<DisplayListener> displayListenerCaptor =
                 ArgumentCaptor.forClass(DisplayListener.class);
-        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+        verify(mInjector, atLeastOnce()).registerDisplayListener(displayListenerCaptor.capture(),
                 any(Handler.class));
-        DisplayListener displayListener = displayListenerCaptor.getValue();
+        // DisplayObserver should register first
+        DisplayListener displayListener = displayListenerCaptor.getAllValues().get(0);
 
         float refreshRate = 60;
         mInjector.mDisplayInfo.layoutLimitedRefreshRate =
@@ -3417,9 +3420,10 @@
 
         ArgumentCaptor<DisplayListener> displayListenerCaptor =
                 ArgumentCaptor.forClass(DisplayListener.class);
-        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+        verify(mInjector, atLeastOnce()).registerDisplayListener(displayListenerCaptor.capture(),
                 any(Handler.class));
-        DisplayListener displayListener = displayListenerCaptor.getValue();
+        // DisplayObserver should register first
+        DisplayListener displayListener = displayListenerCaptor.getAllValues().get(0);
 
         mInjector.mDisplayInfo.layoutLimitedRefreshRate = new RefreshRateRange(10, 10);
         mInjector.mDisplayInfoValid = false;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
index 2d317af..ee79d19 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
@@ -407,7 +407,8 @@
             assertThat(mObserver).isNull();
             mObserver = invocation.getArgument(0);
             return null;
-        }).when(mInjector).registerDisplayListener(any(), any());
+        }).when(mInjector).registerDisplayListener(
+                any(DisplayModeDirector.DisplayObserver.class), any());
 
         doAnswer(c -> {
             DisplayInfo info = c.getArgument(1);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
index 4d910ce..e431c8c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
@@ -27,8 +27,11 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.util.test.FakeSettingsProvider
 import com.android.server.display.DisplayDeviceConfig
+import com.android.server.display.config.RefreshRateData
+import com.android.server.display.config.SupportedModeData
 import com.android.server.display.feature.DisplayManagerFlags
 import com.android.server.display.mode.DisplayModeDirector.DisplayDeviceConfigProvider
+import com.android.server.display.mode.SupportedRefreshRatesVote.RefreshRates
 import com.android.server.testutils.TestHandler
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
@@ -69,6 +72,13 @@
 private val RANGES_MIN60_60TO90 = RefreshRateRanges(RANGE_60_INF, RANGE_60_90)
 private val RANGES_MIN90_90TO90 = RefreshRateRanges(RANGE_90_INF, RANGE_90_90)
 
+private val LOW_POWER_GLOBAL_VOTE = Vote.forRenderFrameRates(0f, 60f)
+private val LOW_POWER_REFRESH_RATE_DATA = createRefreshRateData(
+    lowPowerSupportedModes = listOf(SupportedModeData(60f, 60f), SupportedModeData(60f, 240f)))
+private val LOW_POWER_EMPTY_REFRESH_RATE_DATA = createRefreshRateData()
+private val EXPECTED_SUPPORTED_MODES_VOTE = SupportedRefreshRatesVote(
+    listOf(RefreshRates(60f, 60f), RefreshRates(60f, 240f)))
+
 @SmallTest
 @RunWith(TestParameterInjector::class)
 class SettingsObserverTest {
@@ -103,7 +113,7 @@
         val displayModeDirector = DisplayModeDirector(
                 spyContext, testHandler, mockInjector, mockFlags, mockDisplayDeviceConfigProvider)
         val ddcByDisplay = SparseArray<DisplayDeviceConfig>()
-        whenever(mockDeviceConfig.isVrrSupportEnabled).thenReturn(testCase.vrrSupported)
+        whenever(mockDeviceConfig.refreshRateData).thenReturn(testCase.refreshRateData)
         ddcByDisplay.put(Display.DEFAULT_DISPLAY, mockDeviceConfig)
         displayModeDirector.injectDisplayDeviceConfigByDisplay(ddcByDisplay)
         val settingsObserver = displayModeDirector.SettingsObserver(
@@ -113,27 +123,30 @@
                 false, Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE), 1)
 
         assertThat(displayModeDirector.getVote(VotesStorage.GLOBAL_ID,
-                Vote.PRIORITY_LOW_POWER_MODE)).isEqualTo(testCase.expectedVote)
+                Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE)).isEqualTo(testCase.globalVote)
+        assertThat(displayModeDirector.getVote(Display.DEFAULT_DISPLAY,
+            Vote.PRIORITY_LOW_POWER_MODE_MODES)).isEqualTo(testCase.displayVote)
     }
 
     enum class LowPowerTestCase(
-        val vrrSupported: Boolean,
+        val refreshRateData: RefreshRateData,
         val vsyncLowPowerVoteEnabled: Boolean,
         val lowPowerModeEnabled: Boolean,
-        internal val expectedVote: Vote?
+        internal val globalVote: Vote?,
+        internal val displayVote: Vote?
     ) {
-        ALL_ENABLED(true, true, true,
-            SupportedRefreshRatesVote(listOf(
-                SupportedRefreshRatesVote.RefreshRates(60f, 240f),
-                SupportedRefreshRatesVote.RefreshRates(60f, 60f)
-            ))),
-        LOW_POWER_OFF(true, true, false, null),
-        DVRR_NOT_SUPPORTED_LOW_POWER_ON(false, true, true,
-            RefreshRateVote.RenderVote(0f, 60f)),
-        DVRR_NOT_SUPPORTED_LOW_POWER_OFF(false, true, false, null),
-        VSYNC_VOTE_DISABLED_SUPPORTED_LOW_POWER_ON(true, false, true,
-            RefreshRateVote.RenderVote(0f, 60f)),
-        VSYNC_VOTE_DISABLED_LOW_POWER_OFF(true, false, false, null),
+        ALL_ENABLED(LOW_POWER_REFRESH_RATE_DATA, true, true,
+            LOW_POWER_GLOBAL_VOTE, EXPECTED_SUPPORTED_MODES_VOTE),
+        LOW_POWER_OFF(LOW_POWER_REFRESH_RATE_DATA, true, false,
+            null, null),
+        EMPTY_REFRESH_LOW_POWER_ON(LOW_POWER_EMPTY_REFRESH_RATE_DATA, true, true,
+            LOW_POWER_GLOBAL_VOTE, null),
+        EMPTY_REFRESH__LOW_POWER_OFF(LOW_POWER_EMPTY_REFRESH_RATE_DATA, true, false,
+            null, null),
+        VSYNC_VOTE_DISABLED_SUPPORTED_LOW_POWER_ON(LOW_POWER_REFRESH_RATE_DATA, false, true,
+            LOW_POWER_GLOBAL_VOTE, null),
+        VSYNC_VOTE_DISABLED_LOW_POWER_OFF(LOW_POWER_REFRESH_RATE_DATA, false, false,
+            null, null),
     }
 
     @Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt
index 6b90bde..1206e30 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt
@@ -16,6 +16,9 @@
 
 package com.android.server.display.mode
 
+import com.android.server.display.config.RefreshRateData
+import com.android.server.display.config.SupportedModeData
+
 internal fun createVotesSummary(
         isDisplayResolutionRangeVotingEnabled: Boolean = true,
         supportedModesVoteEnabled: Boolean = true,
@@ -24,4 +27,16 @@
 ): VoteSummary {
     return VoteSummary(isDisplayResolutionRangeVotingEnabled, supportedModesVoteEnabled,
             loggingEnabled, supportsFrameRateOverride)
-}
\ No newline at end of file
+}
+
+fun createRefreshRateData(
+        defaultRefreshRate: Int = 60,
+        defaultPeakRefreshRate: Int = 60,
+        defaultRefreshRateInHbmHdr: Int = 60,
+        defaultRefreshRateInHbmSunlight: Int = 60,
+        lowPowerSupportedModes: List<SupportedModeData> = emptyList()
+): RefreshRateData {
+        return RefreshRateData(defaultRefreshRate, defaultPeakRefreshRate,
+                defaultRefreshRateInHbmHdr, defaultRefreshRateInHbmSunlight,
+                lowPowerSupportedModes)
+}
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java
index 88ab871..874e991 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java
@@ -273,28 +273,36 @@
     }
 
     @Test
-    public void setDreamHasFocus_true_dreamHasFocus() {
+    public void setDreamIsObscured_true_dreamIsNotFrontmost() {
         mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
                 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
 
-        mDreamController.setDreamHasFocus(true);
-        assertTrue(mDreamController.dreamHasFocus());
+        mDreamController.setDreamIsObscured(true);
+        assertFalse(mDreamController.dreamIsFrontmost());
     }
 
     @Test
-    public void setDreamHasFocus_false_dreamDoesNotHaveFocus() {
+    public void setDreamIsObscured_false_dreamIsFrontmost() {
         mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
                 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
 
-        mDreamController.setDreamHasFocus(false);
-        assertFalse(mDreamController.dreamHasFocus());
+        mDreamController.setDreamIsObscured(false);
+        assertTrue(mDreamController.dreamIsFrontmost());
     }
 
     @Test
-    public void setDreamHasFocus_notDreaming_dreamDoesNotHaveFocus() {
-        mDreamController.setDreamHasFocus(true);
-        // Dream still doesn't have focus because it was never started.
-        assertFalse(mDreamController.dreamHasFocus());
+    public void setDreamIsObscured_notDreaming_dreamIsNotFrontmost() {
+        mDreamController.setDreamIsObscured(true);
+        // Dream still isn't frontmost because it was never started.
+        assertFalse(mDreamController.dreamIsFrontmost());
+    }
+
+    @Test
+    public void startDream_dreamIsFrontmost() {
+        mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
+                0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
+
+        assertTrue(mDreamController.dreamIsFrontmost());
     }
 
     private ServiceConnection captureServiceConnection() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 4a21645..42814e7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -239,6 +239,9 @@
 
     @Test
     public void testBootLoopDetectionWithExecutionForAllRescueLevels() {
+        // this is old test where the flag needs to be disabled
+        mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+
         RescueParty.onSettingsProviderPublished(mMockContext);
         verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
                 any(Executor.class),
@@ -449,6 +452,9 @@
 
     @Test
     public void testNonPersistentAppCrashDetectionWithScopedResets() {
+        // this is old test where the flag needs to be disabled
+        mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+
         RescueParty.onSettingsProviderPublished(mMockContext);
         verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
                 any(Executor.class),
@@ -506,6 +512,9 @@
 
     @Test
     public void testNonDeviceConfigSettingsOnlyResetOncePerLevel() {
+        // this is old test where the flag needs to be disabled
+        mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+
         RescueParty.onSettingsProviderPublished(mMockContext);
         verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
                 any(Executor.class),
@@ -879,6 +888,9 @@
 
     @Test
     public void testBootLoopLevels() {
+        // this is old test where the flag needs to be disabled
+        mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+
         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
 
         assertEquals(observer.onBootLoop(0), PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
index c1f4fee..e88e28b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -142,6 +142,7 @@
         final String app1PackageName = "com.android.test.stub1";
         final long appStartTimestampIntentStarted = 1000000;
         final long appStartTimestampActivityLaunchFinished = 2000000;
+        final long appStartTimestampFirstFrameDrawn = 2500000;
         final long appStartTimestampReportFullyDrawn = 3000000;
         final long appStartTimestampService = 4000000;
         final long appStartTimestampBroadcast = 5000000;
@@ -272,6 +273,8 @@
 
         mAppStartInfoTracker.onActivityLaunchFinished(appStartTimestampIntentStarted, COMPONENT,
                 appStartTimestampActivityLaunchFinished, ApplicationStartInfo.LAUNCH_MODE_STANDARD);
+        mAppStartInfoTracker.addTimestampToStart(app1PackageName, app1Uid,
+                appStartTimestampFirstFrameDrawn, ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME);
         list.clear();
         mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
         verifyInProgressRecordsSize(1);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index cc69c1d..28c7fb2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -956,6 +956,7 @@
         ConnectionRecord cr = s.getConnections().get(binder).get(0);
         setFieldValue(ConnectionRecord.class, cr, "activity",
                 mock(ActivityServiceConnectionsHolder.class));
+        doReturn(client).when(sService).getTopApp();
         doReturn(true).when(cr.activity).isActivityVisible();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         updateOomAdj(client, app);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/OWNERS b/services/tests/mockingservicestests/src/com/android/server/am/OWNERS
index 72c0a9e..2cbc226 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/am/OWNERS
@@ -1 +1,3 @@
 include /services/core/java/com/android/server/am/OWNERS
+
+per-file ApplicationStartInfoTest.java = yforta@google.com, carmenjackson@google.com, jji@google.com
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
index 599b9cd..8e1e339 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
@@ -273,10 +273,8 @@
     keyValueMap.put("namespace_1*flag_1", "true");
     // case 2: existing prop, stage a different value
     keyValueMap.put("namespace_1*flag_2", "false");
-    // case 3: new prop, stage the non default value
+    // case 3: new prop
     keyValueMap.put("namespace_2*flag_1", "true");
-    // case 4: new prop, stage the default value
-    keyValueMap.put("namespace_2*flag_2", "false");
     Properties props = new Properties(namespace, keyValueMap);
 
     HashMap<String, HashMap<String, String>> toStageProps =
@@ -290,11 +288,9 @@
     String namespace_1_flag_1 = namespace_1_to_stage.get("flag_1");
     String namespace_1_flag_2 = namespace_1_to_stage.get("flag_2");
     String namespace_2_flag_1 = namespace_2_to_stage.get("flag_1");
-    String namespace_2_flag_2 = namespace_2_to_stage.get("flag_2");
     Assert.assertTrue(namespace_1_flag_1 == null);
     Assert.assertTrue(namespace_1_flag_2 != null);
     Assert.assertTrue(namespace_2_flag_1 != null);
-    Assert.assertTrue(namespace_2_flag_2 == null);
     Assert.assertTrue(namespace_1_flag_2.equals("false"));
     Assert.assertTrue(namespace_2_flag_1.equals("true"));
   }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 11f20e3..d15c24b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -31,6 +31,7 @@
 import static com.android.server.job.JobSchedulerService.sUptimeMillisClock;
 import static com.android.server.job.Flags.FLAG_BATCH_ACTIVE_BUCKET_JOBS;
 import static com.android.server.job.Flags.FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK;
+import static com.android.server.job.Flags.FLAG_THERMAL_RESTRICTIONS_TO_FGS_JOBS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -74,6 +75,9 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.platform.test.flag.junit.SetFlagsRule;
 
 import com.android.server.AppStateTracker;
@@ -85,6 +89,8 @@
 import com.android.server.job.controllers.ConnectivityController;
 import com.android.server.job.controllers.JobStatus;
 import com.android.server.job.controllers.QuotaController;
+import com.android.server.job.restrictions.JobRestriction;
+import com.android.server.job.restrictions.ThermalStatusRestriction;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.usage.AppStandbyInternal;
 
@@ -121,6 +127,9 @@
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private ChargingPolicyChangeListener mChargingPolicyChangeListener;
 
     private class TestJobSchedulerService extends JobSchedulerService {
@@ -2385,6 +2394,108 @@
         assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job2b));
     }
 
+    /**
+     * Unit tests {@link JobSchedulerService#checkIfRestricted(JobStatus)} with single {@link
+     * JobRestriction} registered.
+     */
+    @Test
+    public void testCheckIfRestrictedSingleRestriction() {
+        int bias = JobInfo.BIAS_BOUND_FOREGROUND_SERVICE;
+        JobStatus fgsJob =
+                createJobStatus(
+                        "testCheckIfRestrictedSingleRestriction", createJobInfo(1).setBias(bias));
+        ThermalStatusRestriction mockThermalStatusRestriction =
+                mock(ThermalStatusRestriction.class);
+        mService.mJobRestrictions.clear();
+        mService.mJobRestrictions.add(mockThermalStatusRestriction);
+        when(mockThermalStatusRestriction.isJobRestricted(fgsJob, bias)).thenReturn(true);
+
+        synchronized (mService.mLock) {
+            assertEquals(mService.checkIfRestricted(fgsJob), mockThermalStatusRestriction);
+        }
+
+        when(mockThermalStatusRestriction.isJobRestricted(fgsJob, bias)).thenReturn(false);
+        synchronized (mService.mLock) {
+            assertNull(mService.checkIfRestricted(fgsJob));
+        }
+    }
+
+    /**
+     * Unit tests {@link JobSchedulerService#checkIfRestricted(JobStatus)} with multiple {@link
+     * JobRestriction} registered.
+     */
+    @Test
+    public void testCheckIfRestrictedMultipleRestrictions() {
+        int bias = JobInfo.BIAS_BOUND_FOREGROUND_SERVICE;
+        JobStatus fgsJob =
+                createJobStatus(
+                        "testGetMinJobExecutionGuaranteeMs", createJobInfo(1).setBias(bias));
+        JobRestriction mock1JobRestriction = mock(JobRestriction.class);
+        JobRestriction mock2JobRestriction = mock(JobRestriction.class);
+        mService.mJobRestrictions.clear();
+        mService.mJobRestrictions.add(mock1JobRestriction);
+        mService.mJobRestrictions.add(mock2JobRestriction);
+
+        // Jobs will be restricted if any one of the registered {@link JobRestriction}
+        // reports true.
+        when(mock1JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(true);
+        when(mock2JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(false);
+        synchronized (mService.mLock) {
+            assertEquals(mService.checkIfRestricted(fgsJob), mock1JobRestriction);
+        }
+
+        when(mock1JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(false);
+        when(mock2JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(true);
+        synchronized (mService.mLock) {
+            assertEquals(mService.checkIfRestricted(fgsJob), mock2JobRestriction);
+        }
+
+        when(mock1JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(false);
+        when(mock2JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(false);
+        synchronized (mService.mLock) {
+            assertNull(mService.checkIfRestricted(fgsJob));
+        }
+
+        when(mock1JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(true);
+        when(mock2JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(true);
+        synchronized (mService.mLock) {
+            assertNotEquals(mService.checkIfRestricted(fgsJob), mock1JobRestriction);
+        }
+    }
+
+    /**
+     * Jobs with foreground service and top app biases must not be restricted when the flag is
+     * disabled.
+     */
+    @Test
+    @RequiresFlagsDisabled(FLAG_THERMAL_RESTRICTIONS_TO_FGS_JOBS)
+    public void testCheckIfRestricted_highJobBias_flagThermalRestrictionsToFgsJobsDisabled() {
+        JobStatus fgsJob =
+                createJobStatus(
+                        "testCheckIfRestrictedJobBiasFgs",
+                        createJobInfo(1).setBias(JobInfo.BIAS_FOREGROUND_SERVICE));
+        JobStatus topAppJob =
+                createJobStatus(
+                        "testCheckIfRestrictedJobBiasTopApp",
+                        createJobInfo(2).setBias(JobInfo.BIAS_TOP_APP));
+
+        synchronized (mService.mLock) {
+            assertNull(mService.checkIfRestricted(fgsJob));
+            assertNull(mService.checkIfRestricted(topAppJob));
+        }
+    }
+
+    /** Jobs with top app biases must not be restricted. */
+    @Test
+    public void testCheckIfRestricted_highJobBias() {
+        JobStatus topAppJob = createJobStatus(
+                "testCheckIfRestrictedJobBiasTopApp",
+                createJobInfo(1).setBias(JobInfo.BIAS_TOP_APP));
+        synchronized (mService.mLock) {
+            assertNull(mService.checkIfRestricted(topAppJob));
+        }
+    }
+
     private void setBatteryLevel(int level) {
         doReturn(level).when(mBatteryManagerInternal).getBatteryLevel();
         mService.mBatteryStateTracker
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
index 754f409..c2c67e6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
@@ -28,6 +28,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.job.Flags.FLAG_THERMAL_RESTRICTIONS_TO_FGS_JOBS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -43,7 +44,12 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.PowerManager;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.DeviceConfig;
+import android.util.DebugUtils;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -53,6 +59,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -76,6 +83,157 @@
     @Mock
     private JobSchedulerService mJobSchedulerService;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    class JobStatusContainer {
+        public final JobStatus jobMinPriority;
+        public final JobStatus jobLowPriority;
+        public final JobStatus jobLowPriorityRunning;
+        public final JobStatus jobLowPriorityRunningLong;
+        public final JobStatus jobDefaultPriority;
+        public final JobStatus jobHighPriority;
+        public final JobStatus jobHighPriorityRunning;
+        public final JobStatus jobHighPriorityRunningLong;
+        public final JobStatus ejDowngraded;
+        public final JobStatus ej;
+        public final JobStatus ejRetried;
+        public final JobStatus ejRunning;
+        public final JobStatus ejRunningLong;
+        public final JobStatus ui;
+        public final JobStatus uiRetried;
+        public final JobStatus uiRunning;
+        public final JobStatus uiRunningLong;
+        public final JobStatus importantWhileForeground;
+        public final JobStatus importantWhileForegroundRunning;
+        public final JobStatus importantWhileForegroundRunningLong;
+        public final int[] allJobBiases = {
+            JobInfo.BIAS_ADJ_ALWAYS_RUNNING,
+            JobInfo.BIAS_ADJ_OFTEN_RUNNING,
+            JobInfo.BIAS_DEFAULT,
+            JobInfo.BIAS_SYNC_EXPEDITED,
+            JobInfo.BIAS_SYNC_INITIALIZATION,
+            JobInfo.BIAS_BOUND_FOREGROUND_SERVICE,
+            JobInfo.BIAS_FOREGROUND_SERVICE,
+            JobInfo.BIAS_TOP_APP
+        };
+        public final int[] biasesBelowFgs = {
+            JobInfo.BIAS_ADJ_ALWAYS_RUNNING,
+            JobInfo.BIAS_ADJ_OFTEN_RUNNING,
+            JobInfo.BIAS_DEFAULT,
+            JobInfo.BIAS_SYNC_EXPEDITED,
+            JobInfo.BIAS_SYNC_INITIALIZATION,
+            JobInfo.BIAS_BOUND_FOREGROUND_SERVICE
+        };
+        public final int[] thermalStatuses = {
+            THERMAL_STATUS_NONE,
+            THERMAL_STATUS_LIGHT,
+            THERMAL_STATUS_MODERATE,
+            THERMAL_STATUS_SEVERE,
+            THERMAL_STATUS_CRITICAL,
+            THERMAL_STATUS_EMERGENCY,
+            THERMAL_STATUS_SHUTDOWN
+        };
+
+        JobStatusContainer(String jobName, JobSchedulerService mJobSchedulerService) {
+            jobMinPriority =
+                    createJobStatus(
+                            jobName, createJobBuilder(1).setPriority(JobInfo.PRIORITY_MIN).build());
+            jobLowPriority =
+                    createJobStatus(
+                            jobName, createJobBuilder(2).setPriority(JobInfo.PRIORITY_LOW).build());
+            jobLowPriorityRunning =
+                    createJobStatus(
+                            jobName, createJobBuilder(3).setPriority(JobInfo.PRIORITY_LOW).build());
+            jobLowPriorityRunningLong =
+                    createJobStatus(
+                            jobName, createJobBuilder(9).setPriority(JobInfo.PRIORITY_LOW).build());
+            jobDefaultPriority =
+                    createJobStatus(
+                            jobName,
+                            createJobBuilder(4).setPriority(JobInfo.PRIORITY_DEFAULT).build());
+            jobHighPriority =
+                    createJobStatus(
+                            jobName,
+                            createJobBuilder(5).setPriority(JobInfo.PRIORITY_HIGH).build());
+            jobHighPriorityRunning =
+                    createJobStatus(
+                            jobName,
+                            createJobBuilder(6).setPriority(JobInfo.PRIORITY_HIGH).build());
+            jobHighPriorityRunningLong =
+                    createJobStatus(
+                            jobName,
+                            createJobBuilder(10).setPriority(JobInfo.PRIORITY_HIGH).build());
+            ejDowngraded = createJobStatus(jobName, createJobBuilder(7).setExpedited(true).build());
+            ej = spy(createJobStatus(jobName, createJobBuilder(8).setExpedited(true).build()));
+            ejRetried =
+                    spy(createJobStatus(jobName, createJobBuilder(11).setExpedited(true).build()));
+            ejRunning =
+                    spy(createJobStatus(jobName, createJobBuilder(12).setExpedited(true).build()));
+            ejRunningLong =
+                    spy(createJobStatus(jobName, createJobBuilder(13).setExpedited(true).build()));
+            ui = spy(createJobStatus(jobName, createJobBuilder(14).build()));
+            uiRetried = spy(createJobStatus(jobName, createJobBuilder(15).build()));
+            uiRunning = spy(createJobStatus(jobName, createJobBuilder(16).build()));
+            uiRunningLong = spy(createJobStatus(jobName, createJobBuilder(17).build()));
+            importantWhileForeground = spy(createJobStatus(jobName, createJobBuilder(18)
+                    .setImportantWhileForeground(true)
+                    .build()));
+            importantWhileForegroundRunning = spy(createJobStatus(jobName, createJobBuilder(20)
+                    .setImportantWhileForeground(true)
+                     .build()));
+            importantWhileForegroundRunningLong = spy(createJobStatus(jobName, createJobBuilder(19)
+                     .setImportantWhileForeground(true)
+                     .build()));
+
+            when(ej.shouldTreatAsExpeditedJob()).thenReturn(true);
+            when(ejRetried.shouldTreatAsExpeditedJob()).thenReturn(true);
+            when(ejRunning.shouldTreatAsExpeditedJob()).thenReturn(true);
+            when(ejRunningLong.shouldTreatAsExpeditedJob()).thenReturn(true);
+            when(ui.shouldTreatAsUserInitiatedJob()).thenReturn(true);
+            when(uiRetried.shouldTreatAsUserInitiatedJob()).thenReturn(true);
+            when(uiRunning.shouldTreatAsUserInitiatedJob()).thenReturn(true);
+            when(uiRunningLong.shouldTreatAsUserInitiatedJob()).thenReturn(true);
+            when(ejRetried.getNumPreviousAttempts()).thenReturn(1);
+            when(uiRetried.getNumPreviousAttempts()).thenReturn(2);
+            when(mJobSchedulerService.isCurrentlyRunningLocked(jobLowPriorityRunning))
+                    .thenReturn(true);
+            when(mJobSchedulerService.isCurrentlyRunningLocked(jobHighPriorityRunning))
+                    .thenReturn(true);
+            when(mJobSchedulerService.isCurrentlyRunningLocked(jobLowPriorityRunningLong))
+                    .thenReturn(true);
+            when(mJobSchedulerService.isCurrentlyRunningLocked(jobHighPriorityRunningLong))
+                    .thenReturn(true);
+            when(mJobSchedulerService.isCurrentlyRunningLocked(ejRunning)).thenReturn(true);
+            when(mJobSchedulerService.isCurrentlyRunningLocked(ejRunningLong)).thenReturn(true);
+            when(mJobSchedulerService.isCurrentlyRunningLocked(uiRunning)).thenReturn(true);
+            when(mJobSchedulerService.isCurrentlyRunningLocked(uiRunningLong)).thenReturn(true);
+            when(mJobSchedulerService.isJobInOvertimeLocked(jobLowPriorityRunningLong))
+                    .thenReturn(true);
+            when(mJobSchedulerService.isCurrentlyRunningLocked(importantWhileForegroundRunning))
+                    .thenReturn(true);
+            when(mJobSchedulerService.isJobInOvertimeLocked(jobHighPriorityRunningLong))
+                    .thenReturn(true);
+            when(mJobSchedulerService.isJobInOvertimeLocked(ejRunningLong)).thenReturn(true);
+            when(mJobSchedulerService.isJobInOvertimeLocked(uiRunningLong)).thenReturn(true);
+            when(mJobSchedulerService.isCurrentlyRunningLocked(importantWhileForegroundRunningLong))
+                    .thenReturn(true);
+            when(mJobSchedulerService.isJobInOvertimeLocked(importantWhileForegroundRunningLong))
+                    .thenReturn(true);
+        }
+    }
+
+    private boolean isJobRestricted(JobStatus status, int bias) {
+        return mThermalStatusRestriction.isJobRestricted(status, bias);
+    }
+
+    private static String debugTag(int bias, @PowerManager.ThermalStatus int status) {
+        return "Bias = "
+                + JobInfo.getBiasString(bias)
+                + " Thermal Status = "
+                + DebugUtils.valueToString(PowerManager.class, "THERMAL_STATUS_", status);
+    }
+
     @Before
     public void setUp() {
         mMockingSession = mockitoSession()
@@ -156,169 +314,302 @@
         assertEquals(THERMAL_STATUS_EMERGENCY, mThermalStatusRestriction.getThermalStatus());
     }
 
+    /**
+     * Test {@link JobSchedulerService#isJobRestricted(JobStatus)} when Thermal is in default state
+     */
     @Test
-    public void testIsJobRestricted() {
+    public void testIsJobRestrictedDefaultStates() {
         mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_NONE);
+        JobStatusContainer jc = new JobStatusContainer("testIsJobRestricted", mJobSchedulerService);
 
-        final JobStatus jobMinPriority = createJobStatus("testIsJobRestricted",
-                createJobBuilder(1).setPriority(JobInfo.PRIORITY_MIN).build());
-        final JobStatus jobLowPriority = createJobStatus("testIsJobRestricted",
-                createJobBuilder(2).setPriority(JobInfo.PRIORITY_LOW).build());
-        final JobStatus jobLowPriorityRunning = createJobStatus("testIsJobRestricted",
-                createJobBuilder(3).setPriority(JobInfo.PRIORITY_LOW).build());
-        final JobStatus jobLowPriorityRunningLong = createJobStatus("testIsJobRestricted",
-                createJobBuilder(9).setPriority(JobInfo.PRIORITY_LOW).build());
-        final JobStatus jobDefaultPriority = createJobStatus("testIsJobRestricted",
-                createJobBuilder(4).setPriority(JobInfo.PRIORITY_DEFAULT).build());
-        final JobStatus jobHighPriority = createJobStatus("testIsJobRestricted",
-                createJobBuilder(5).setPriority(JobInfo.PRIORITY_HIGH).build());
-        final JobStatus jobHighPriorityRunning = createJobStatus("testIsJobRestricted",
-                createJobBuilder(6).setPriority(JobInfo.PRIORITY_HIGH).build());
-        final JobStatus jobHighPriorityRunningLong = createJobStatus("testIsJobRestricted",
-                createJobBuilder(10).setPriority(JobInfo.PRIORITY_HIGH).build());
-        final JobStatus ejDowngraded = createJobStatus("testIsJobRestricted",
-                createJobBuilder(7).setExpedited(true).build());
-        final JobStatus ej = spy(createJobStatus("testIsJobRestricted",
-                createJobBuilder(8).setExpedited(true).build()));
-        final JobStatus ejRetried = spy(createJobStatus("testIsJobRestricted",
-                createJobBuilder(11).setExpedited(true).build()));
-        final JobStatus ejRunning = spy(createJobStatus("testIsJobRestricted",
-                createJobBuilder(12).setExpedited(true).build()));
-        final JobStatus ejRunningLong = spy(createJobStatus("testIsJobRestricted",
-                createJobBuilder(13).setExpedited(true).build()));
-        final JobStatus ui = spy(createJobStatus("testIsJobRestricted",
-                createJobBuilder(14).build()));
-        final JobStatus uiRetried = spy(createJobStatus("testIsJobRestricted",
-                createJobBuilder(15).build()));
-        final JobStatus uiRunning = spy(createJobStatus("testIsJobRestricted",
-                createJobBuilder(16).build()));
-        final JobStatus uiRunningLong = spy(createJobStatus("testIsJobRestricted",
-                createJobBuilder(17).build()));
-        when(ej.shouldTreatAsExpeditedJob()).thenReturn(true);
-        when(ejRetried.shouldTreatAsExpeditedJob()).thenReturn(true);
-        when(ejRunning.shouldTreatAsExpeditedJob()).thenReturn(true);
-        when(ejRunningLong.shouldTreatAsExpeditedJob()).thenReturn(true);
-        when(ui.shouldTreatAsUserInitiatedJob()).thenReturn(true);
-        when(uiRetried.shouldTreatAsUserInitiatedJob()).thenReturn(true);
-        when(uiRunning.shouldTreatAsUserInitiatedJob()).thenReturn(true);
-        when(uiRunningLong.shouldTreatAsUserInitiatedJob()).thenReturn(true);
-        when(ejRetried.getNumPreviousAttempts()).thenReturn(1);
-        when(uiRetried.getNumPreviousAttempts()).thenReturn(2);
-        when(mJobSchedulerService.isCurrentlyRunningLocked(jobLowPriorityRunning)).thenReturn(true);
-        when(mJobSchedulerService.isCurrentlyRunningLocked(jobHighPriorityRunning))
-                .thenReturn(true);
-        when(mJobSchedulerService.isCurrentlyRunningLocked(jobLowPriorityRunningLong))
-                .thenReturn(true);
-        when(mJobSchedulerService.isCurrentlyRunningLocked(jobHighPriorityRunningLong))
-                .thenReturn(true);
-        when(mJobSchedulerService.isCurrentlyRunningLocked(ejRunning)).thenReturn(true);
-        when(mJobSchedulerService.isCurrentlyRunningLocked(ejRunningLong)).thenReturn(true);
-        when(mJobSchedulerService.isCurrentlyRunningLocked(uiRunning)).thenReturn(true);
-        when(mJobSchedulerService.isCurrentlyRunningLocked(uiRunningLong)).thenReturn(true);
-        when(mJobSchedulerService.isJobInOvertimeLocked(jobLowPriorityRunningLong))
-                .thenReturn(true);
-        when(mJobSchedulerService.isJobInOvertimeLocked(jobHighPriorityRunningLong))
-                .thenReturn(true);
-        when(mJobSchedulerService.isJobInOvertimeLocked(ejRunningLong)).thenReturn(true);
-        when(mJobSchedulerService.isJobInOvertimeLocked(uiRunningLong)).thenReturn(true);
+        for (int jobBias : jc.allJobBiases) {
+            assertFalse(isJobRestricted(jc.jobMinPriority, jobBias));
+            assertFalse(isJobRestricted(jc.jobLowPriority, jobBias));
+            assertFalse(isJobRestricted(jc.jobLowPriorityRunning, jobBias));
+            assertFalse(isJobRestricted(jc.jobLowPriorityRunningLong, jobBias));
+            assertFalse(isJobRestricted(jc.jobDefaultPriority, jobBias));
+            assertFalse(isJobRestricted(jc.jobHighPriority, jobBias));
+            assertFalse(isJobRestricted(jc.jobHighPriorityRunning, jobBias));
+            assertFalse(isJobRestricted(jc.jobHighPriorityRunningLong, jobBias));
+            assertFalse(isJobRestricted(jc.importantWhileForeground, jobBias));
+            assertFalse(isJobRestricted(jc.importantWhileForegroundRunning, jobBias));
+            assertFalse(isJobRestricted(jc.importantWhileForegroundRunningLong, jobBias));
+            assertFalse(isJobRestricted(jc.ej, jobBias));
+            assertFalse(isJobRestricted(jc.ejDowngraded, jobBias));
+            assertFalse(isJobRestricted(jc.ejRetried, jobBias));
+            assertFalse(isJobRestricted(jc.ejRunning, jobBias));
+            assertFalse(isJobRestricted(jc.ejRunningLong, jobBias));
+            assertFalse(isJobRestricted(jc.ui, jobBias));
+            assertFalse(isJobRestricted(jc.uiRetried, jobBias));
+            assertFalse(isJobRestricted(jc.uiRunning, jobBias));
+            assertFalse(isJobRestricted(jc.uiRunningLong, jobBias));
+        }
+    }
 
-        assertFalse(mThermalStatusRestriction.isJobRestricted(jobMinPriority));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(jobLowPriority));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunning));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunningLong));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(jobDefaultPriority));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriority));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunning));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(ej));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(ejDowngraded));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(ejRetried));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunning));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunningLong));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(ui));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(uiRetried));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunning));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunningLong));
+    /**
+     * Test {@link JobSchedulerService#isJobRestricted(JobStatus)} when Job Bias is Top App and all
+     * Thermal states.
+     */
+    @Test
+    public void testIsJobRestrictedBiasTopApp() {
+        JobStatusContainer jc =
+                new JobStatusContainer("testIsJobRestrictedBiasTopApp", mJobSchedulerService);
 
-        mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_LIGHT);
+        int jobBias = JobInfo.BIAS_TOP_APP;
+        for (int thermalStatus : jc.thermalStatuses) {
+            String msg = "Thermal Status = " + DebugUtils.valueToString(
+                    PowerManager.class, "THERMAL_STATUS_", thermalStatus);
+            mStatusChangedListener.onThermalStatusChanged(thermalStatus);
 
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobMinPriority));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriority));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunning));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunningLong));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(jobDefaultPriority));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriority));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunning));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(ejDowngraded));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(ej));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(ejRetried));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunning));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunningLong));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(ui));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(uiRetried));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunning));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunningLong));
+            // No restrictions on any jobs
+            assertFalse(msg, isJobRestricted(jc.jobMinPriority, jobBias));
+            assertFalse(msg, isJobRestricted(jc.jobLowPriority, jobBias));
+            assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias));
+            assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias));
+            assertFalse(msg, isJobRestricted(jc.jobDefaultPriority, jobBias));
+            assertFalse(msg, isJobRestricted(jc.jobHighPriority, jobBias));
+            assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias));
+            assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias));
+            assertFalse(msg, isJobRestricted(jc.importantWhileForeground, jobBias));
+            assertFalse(msg, isJobRestricted(jc.importantWhileForegroundRunning, jobBias));
+            assertFalse(msg, isJobRestricted(jc.importantWhileForegroundRunningLong, jobBias));
+            assertFalse(msg, isJobRestricted(jc.ej, jobBias));
+            assertFalse(msg, isJobRestricted(jc.ejDowngraded, jobBias));
+            assertFalse(msg, isJobRestricted(jc.ejRetried, jobBias));
+            assertFalse(msg, isJobRestricted(jc.ejRunning, jobBias));
+            assertFalse(msg, isJobRestricted(jc.ejRunningLong, jobBias));
+            assertFalse(msg, isJobRestricted(jc.ui, jobBias));
+            assertFalse(msg, isJobRestricted(jc.uiRetried, jobBias));
+            assertFalse(msg, isJobRestricted(jc.uiRunning, jobBias));
+            assertFalse(msg, isJobRestricted(jc.uiRunningLong, jobBias));
+        }
+    }
 
-        mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_MODERATE);
+    /**
+     * Test {@link JobSchedulerService#isJobRestricted(JobStatus)} when Job Bias is Foreground
+     * Service and all Thermal states.
+     */
+    @Test
+    @RequiresFlagsDisabled(FLAG_THERMAL_RESTRICTIONS_TO_FGS_JOBS)
+    public void testIsJobRestrictedBiasFgs_flagThermalRestrictionsToFgsJobsDisabled() {
+        JobStatusContainer jc =
+                new JobStatusContainer("testIsJobRestrictedBiasFgs", mJobSchedulerService);
 
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobMinPriority));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriority));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunning));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunningLong));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobDefaultPriority));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriority));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunning));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(ejDowngraded));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(ej));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(ejRetried));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunning));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunningLong));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(ui));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(uiRetried));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunning));
-        assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunningLong));
+        int jobBias = JobInfo.BIAS_FOREGROUND_SERVICE;
+        for (int thermalStatus : jc.thermalStatuses) {
+            String msg = "Thermal Status = " + DebugUtils.valueToString(
+                    PowerManager.class, "THERMAL_STATUS_", thermalStatus);
+            mStatusChangedListener.onThermalStatusChanged(thermalStatus);
+            // No restrictions on any jobs
+            assertFalse(msg, isJobRestricted(jc.jobMinPriority, jobBias));
+            assertFalse(msg, isJobRestricted(jc.jobLowPriority, jobBias));
+            assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias));
+            assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias));
+            assertFalse(msg, isJobRestricted(jc.jobDefaultPriority, jobBias));
+            assertFalse(msg, isJobRestricted(jc.jobHighPriority, jobBias));
+            assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias));
+            assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias));
+            assertFalse(msg, isJobRestricted(jc.ej, jobBias));
+            assertFalse(msg, isJobRestricted(jc.ejDowngraded, jobBias));
+            assertFalse(msg, isJobRestricted(jc.ejRetried, jobBias));
+            assertFalse(msg, isJobRestricted(jc.ejRunning, jobBias));
+            assertFalse(msg, isJobRestricted(jc.ejRunningLong, jobBias));
+            assertFalse(msg, isJobRestricted(jc.ui, jobBias));
+            assertFalse(msg, isJobRestricted(jc.uiRetried, jobBias));
+            assertFalse(msg, isJobRestricted(jc.uiRunning, jobBias));
+            assertFalse(msg, isJobRestricted(jc.uiRunningLong, jobBias));
+        }
+    }
 
-        mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_SEVERE);
+    /**
+     * Test {@link JobSchedulerService#isJobRestricted(JobStatus)} when Job Bias is Foreground
+     * Service and all Thermal states.
+     */
+    @Test
+    @RequiresFlagsEnabled(FLAG_THERMAL_RESTRICTIONS_TO_FGS_JOBS)
+    public void testIsJobRestrictedBiasFgs_flagThermalRestrictionsToFgsJobsEnabled() {
+        JobStatusContainer jc =
+                new JobStatusContainer("testIsJobRestrictedBiasFgs", mJobSchedulerService);
+        int jobBias = JobInfo.BIAS_FOREGROUND_SERVICE;
+        for (int thermalStatus : jc.thermalStatuses) {
+            String msg = debugTag(jobBias, thermalStatus);
+            mStatusChangedListener.onThermalStatusChanged(thermalStatus);
+            if (thermalStatus >= THERMAL_STATUS_SEVERE) {
+                // Full restrictions on all jobs
+                assertTrue(msg, isJobRestricted(jc.jobMinPriority, jobBias));
+                assertTrue(msg, isJobRestricted(jc.jobLowPriority, jobBias));
+                assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias));
+                assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias));
+                assertTrue(msg, isJobRestricted(jc.jobDefaultPriority, jobBias));
+                assertTrue(msg, isJobRestricted(jc.jobHighPriority, jobBias));
+                assertTrue(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias));
+                assertTrue(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias));
+                assertTrue(msg, isJobRestricted(jc.ej, jobBias));
+                assertTrue(msg, isJobRestricted(jc.ejDowngraded, jobBias));
+                assertTrue(msg, isJobRestricted(jc.ejRetried, jobBias));
+                assertTrue(msg, isJobRestricted(jc.ejRunning, jobBias));
+                assertTrue(msg, isJobRestricted(jc.ejRunningLong, jobBias));
+                assertTrue(msg, isJobRestricted(jc.ui, jobBias));
+                assertTrue(msg, isJobRestricted(jc.uiRetried, jobBias));
+                assertTrue(msg, isJobRestricted(jc.uiRunning, jobBias));
+                assertTrue(msg, isJobRestricted(jc.uiRunningLong, jobBias));
+            } else if (thermalStatus >= THERMAL_STATUS_MODERATE) {
+                // No restrictions on user related jobs
+                assertFalse(msg, isJobRestricted(jc.ui, jobBias));
+                assertFalse(msg, isJobRestricted(jc.uiRetried, jobBias));
+                assertFalse(msg, isJobRestricted(jc.uiRunning, jobBias));
+                assertFalse(msg, isJobRestricted(jc.uiRunningLong, jobBias));
+                // Some restrictions on expedited jobs
+                assertFalse(msg, isJobRestricted(jc.ej, jobBias));
+                assertTrue(msg, isJobRestricted(jc.ejDowngraded, jobBias));
+                assertTrue(msg, isJobRestricted(jc.ejRetried, jobBias));
+                assertFalse(msg, isJobRestricted(jc.ejRunning, jobBias));
+                assertTrue(msg, isJobRestricted(jc.ejRunningLong, jobBias));
+                // Some restrictions on high priority jobs
+                assertTrue(msg, isJobRestricted(jc.jobHighPriority, jobBias));
+                assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias));
+                assertTrue(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias));
+                // Some restructions on important while foreground jobs
+                assertFalse(isJobRestricted(jc.importantWhileForeground, jobBias));
+                assertFalse(isJobRestricted(jc.importantWhileForegroundRunning, jobBias));
+                assertTrue(isJobRestricted(jc.importantWhileForegroundRunningLong, jobBias));
+                // Full restriction on default priority jobs
+                assertTrue(msg, isJobRestricted(jc.jobDefaultPriority, jobBias));
+                // Full restriction on low priority jobs
+                assertTrue(msg, isJobRestricted(jc.jobLowPriority, jobBias));
+                assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias));
+                assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias));
+                // Full restriction on min priority jobs
+                assertTrue(msg, isJobRestricted(jc.jobMinPriority, jobBias));
+            } else {
+                // thermalStatus < THERMAL_STATUS_MODERATE
+                // No restrictions on any job type
+                assertFalse(msg, isJobRestricted(jc.ui, jobBias));
+                assertFalse(msg, isJobRestricted(jc.uiRetried, jobBias));
+                assertFalse(msg, isJobRestricted(jc.uiRunning, jobBias));
+                assertFalse(msg, isJobRestricted(jc.uiRunningLong, jobBias));
+                assertFalse(msg, isJobRestricted(jc.ej, jobBias));
+                assertFalse(msg, isJobRestricted(jc.ejDowngraded, jobBias));
+                assertFalse(msg, isJobRestricted(jc.ejRetried, jobBias));
+                assertFalse(msg, isJobRestricted(jc.ejRunning, jobBias));
+                assertFalse(msg, isJobRestricted(jc.ejRunningLong, jobBias));
+                assertFalse(msg, isJobRestricted(jc.jobHighPriority, jobBias));
+                assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias));
+                assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias));
+                assertFalse(msg, isJobRestricted(jc.importantWhileForeground, jobBias));
+                assertFalse(msg, isJobRestricted(jc.importantWhileForegroundRunning, jobBias));
+                assertFalse(msg, isJobRestricted(jc.importantWhileForegroundRunningLong, jobBias));
+                assertFalse(msg, isJobRestricted(jc.jobDefaultPriority, jobBias));
+                assertFalse(msg, isJobRestricted(jc.jobLowPriority, jobBias));
+                assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias));
+                assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias));
+                assertFalse(msg, isJobRestricted(jc.jobMinPriority, jobBias));
+            }
+        }
+    }
 
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobMinPriority));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriority));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunning));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunningLong));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobDefaultPriority));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriority));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunning));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(ejDowngraded));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(ej));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(ejRetried));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunning));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunningLong));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(ui));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(uiRetried));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(uiRunning));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(uiRunningLong));
+    /**
+     * Test {@link JobSchedulerService#isJobRestricted(JobStatus)} when Job Bias is less than
+     * Foreground Service and all Thermal states.
+     */
+    @Test
+    public void testIsJobRestrictedBiasLessThanFgs() {
+        JobStatusContainer jc =
+                new JobStatusContainer("testIsJobRestrictedBiasLessThanFgs", mJobSchedulerService);
 
-        mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_CRITICAL);
-
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobMinPriority));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriority));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunning));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunningLong));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobDefaultPriority));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriority));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunning));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(ejDowngraded));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(ej));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(ejRetried));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunning));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunningLong));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(ui));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(uiRetried));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(uiRunning));
-        assertTrue(mThermalStatusRestriction.isJobRestricted(uiRunningLong));
+        for (int jobBias : jc.biasesBelowFgs) {
+            for (int thermalStatus : jc.thermalStatuses) {
+                String msg = debugTag(jobBias, thermalStatus);
+                mStatusChangedListener.onThermalStatusChanged(thermalStatus);
+                if (thermalStatus >= THERMAL_STATUS_SEVERE) {
+                    // Full restrictions on all jobs
+                    assertTrue(msg, isJobRestricted(jc.jobMinPriority, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.jobLowPriority, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.jobDefaultPriority, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.jobHighPriority, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.ej, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.ejDowngraded, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.ejRetried, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.ejRunning, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.ejRunningLong, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.ui, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.uiRetried, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.uiRunning, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.uiRunningLong, jobBias));
+                } else if (thermalStatus >= THERMAL_STATUS_MODERATE) {
+                    // No restrictions on user related jobs
+                    assertFalse(msg, isJobRestricted(jc.ui, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.uiRetried, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.uiRunning, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.uiRunningLong, jobBias));
+                    // Some restrictions on expedited jobs
+                    assertFalse(msg, isJobRestricted(jc.ej, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.ejDowngraded, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.ejRetried, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.ejRunning, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.ejRunningLong, jobBias));
+                    // Some restrictions on high priority jobs
+                    assertTrue(msg, isJobRestricted(jc.jobHighPriority, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias));
+                    // Full restriction on default priority jobs
+                    assertTrue(msg, isJobRestricted(jc.jobDefaultPriority, jobBias));
+                    // Full restriction on low priority jobs
+                    assertTrue(msg, isJobRestricted(jc.jobLowPriority, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias));
+                    // Full restriction on min priority jobs
+                    assertTrue(msg, isJobRestricted(jc.jobMinPriority, jobBias));
+                } else if (thermalStatus >= THERMAL_STATUS_LIGHT) {
+                    // No restrictions on any user related jobs
+                    assertFalse(msg, isJobRestricted(jc.ui, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.uiRetried, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.uiRunning, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.uiRunningLong, jobBias));
+                    // No restrictions on any expedited jobs
+                    assertFalse(msg, isJobRestricted(jc.ej, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.ejDowngraded, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.ejRetried, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.ejRunning, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.ejRunningLong, jobBias));
+                    // No restrictions on any high priority jobs
+                    assertFalse(msg, isJobRestricted(jc.jobHighPriority, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias));
+                    // No restrictions on default priority jobs
+                    assertFalse(msg, isJobRestricted(jc.jobDefaultPriority, jobBias));
+                    // Some restrictions on low priority jobs
+                    assertTrue(msg, isJobRestricted(jc.jobLowPriority, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias));
+                    assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias));
+                    // Full restriction on min priority jobs
+                    assertTrue(msg, isJobRestricted(jc.jobMinPriority, jobBias));
+                } else { // THERMAL_STATUS_NONE
+                    // No restrictions on any jobs
+                    assertFalse(msg, isJobRestricted(jc.jobMinPriority, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.jobLowPriority, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.jobDefaultPriority, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.jobHighPriority, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.ej, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.ejDowngraded, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.ejRetried, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.ejRunning, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.ejRunningLong, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.ui, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.uiRetried, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.uiRunning, jobBias));
+                    assertFalse(msg, isJobRestricted(jc.uiRunningLong, jobBias));
+                }
+            }
+        }
     }
 
     private JobInfo.Builder createJobBuilder(int jobId) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index c9aab53..396edae 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -186,7 +186,7 @@
 
     class Mocks {
         val lock = PackageManagerTracedLock()
-        val installLock = Any()
+        val installLock = PackageManagerTracedLock()
         val injector: PackageManagerServiceInjector = mock()
         val systemWrapper: PackageManagerServiceInjector.SystemWrapper = mock()
         val context: Context = mock()
diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
index a9ff3a1..4460c6a 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
@@ -22,10 +22,12 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
@@ -52,6 +54,7 @@
 import com.android.server.LocalServices;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.power.batterysaver.BatterySaverStateMachine;
+import com.android.server.power.feature.PowerManagerFlags;
 import com.android.server.statusbar.StatusBarManagerInternal;
 
 import org.junit.Before;
@@ -77,6 +80,9 @@
     @Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
     @Mock private Vibrator mVibrator;
     @Mock private StatusBarManagerInternal mStatusBarManagerInternal;
+    @Mock private WakeLockLog mWakeLockLog;
+
+    @Mock private PowerManagerFlags mPowerManagerFlags;
 
     private PowerManagerService mService;
     private Context mContextSpy;
@@ -222,6 +228,7 @@
 
     @Test
     public void testOnWakeLockListener_RemoteException_NoRethrow() {
+        when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true);
         createNotifier();
 
         IWakeLockCallback exceptingCallback = new IWakeLockCallback.Stub() {
@@ -235,6 +242,9 @@
         mNotifier.onWakeLockReleased(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
                 "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
                 exceptingCallback);
+        verifyZeroInteractions(mWakeLockLog);
+        mTestLooper.dispatchAll();
+        verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, 1);
         mNotifier.onWakeLockAcquired(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
                 "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
                 exceptingCallback);
@@ -244,8 +254,27 @@
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
                 "my.package.name", uid, pid, /* newWorkSource= */ null, /* newHistoryTag= */ null,
                 exceptingCallback);
+        verifyNoMoreInteractions(mWakeLockLog);
         mTestLooper.dispatchAll();
+        verify(mWakeLockLog).onWakeLockAcquired("wakelockTag", uid,
+                PowerManager.PARTIAL_WAKE_LOCK, 1);
         // If we didn't throw, we're good!
+
+        // Test with improveWakelockLatency flag false, hence the wakelock log will run on the same
+        // thread
+        clearInvocations(mWakeLockLog);
+        when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(false);
+
+        mNotifier.onWakeLockAcquired(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
+                "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
+                exceptingCallback);
+        verify(mWakeLockLog).onWakeLockAcquired("wakelockTag", uid,
+                PowerManager.PARTIAL_WAKE_LOCK, -1);
+
+        mNotifier.onWakeLockReleased(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
+                "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
+                exceptingCallback);
+        verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, -1);
     }
 
     private final PowerManagerService.Injector mInjector = new PowerManagerService.Injector() {
@@ -253,7 +282,7 @@
         Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
                 SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
                 FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector,
-                Executor backgroundExecutor) {
+                Executor backgroundExecutor, PowerManagerFlags powerManagerFlags) {
             return mNotifierMock;
         }
 
@@ -326,6 +355,18 @@
     }
 
     private void createNotifier() {
+        Notifier.Injector injector = new Notifier.Injector() {
+            @Override
+            public long currentTimeMillis() {
+                return 1;
+            }
+
+            @Override
+            public WakeLockLog getWakeLockLog(Context context) {
+                return mWakeLockLog;
+            }
+        };
+
         mNotifier = new Notifier(
                 mTestLooper.getLooper(),
                 mContextSpy,
@@ -335,7 +376,7 @@
                 null,
                 null,
                 null,
-                mTestExecutor);
+                mTestExecutor, mPowerManagerFlags, injector);
     }
 
     private static class FakeExecutor implements Executor {
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index 7f165e0..b737e0f 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -114,6 +114,7 @@
 import com.android.server.power.batterysaver.BatterySaverController;
 import com.android.server.power.batterysaver.BatterySaverPolicy;
 import com.android.server.power.batterysaver.BatterySaverStateMachine;
+import com.android.server.power.feature.PowerManagerFlags;
 import com.android.server.testutils.OffsettableClock;
 
 import com.google.testing.junit.testparameterinjector.TestParameter;
@@ -275,7 +276,7 @@
             Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
                     SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
                     FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector,
-                    Executor executor) {
+                    Executor executor, PowerManagerFlags powerManagerFlags) {
                 return mNotifierMock;
             }
 
diff --git a/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java
index 0fad25d..1c4db6a 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java
@@ -57,19 +57,42 @@
     }
 
     @Test
+    public void testAddTwoItems_withNoEventTimeSupplied() {
+        final int tagDatabaseSize = 128;
+        final int logSize = 20;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("TagPartial", 101,
+                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, -1);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1150L);
+        log.onWakeLockAcquired("TagFull", 102,
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, -1);
+
+        assertEquals("Wake Lock Log\n"
+                        + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial "
+                        + "(partial,on-after-release)\n"
+                        + "  01-01 00:00:01.150 - 102 (some.package2) - ACQ TagFull "
+                        + "(full,acq-causes-wake)\n"
+                        + "  -\n"
+                        + "  Events: 2, Time-Resets: 0\n"
+                        + "  Buffer, Bytes used: 6\n",
+                dumpLog(log, false));
+    }
+
+    @Test
     public void testAddTwoItems() {
         final int tagDatabaseSize = 128;
         final int logSize = 20;
         TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
         WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
 
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
         log.onWakeLockAcquired("TagPartial", 101,
-                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE);
+                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, 1000L);
 
-        when(injectorSpy.currentTimeMillis()).thenReturn(1150L);
         log.onWakeLockAcquired("TagFull", 102,
-                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, 1150L);
 
         assertEquals("Wake Lock Log\n"
                 + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial "
@@ -89,11 +112,9 @@
         TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
         WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
 
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
-        log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK);
+        log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK, 1000L);
 
-        when(injectorSpy.currentTimeMillis()).thenReturn(1350L);
-        log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK);
+        log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK, 1350L);
 
         assertEquals("Wake Lock Log\n"
                 + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial (partial)\n"
@@ -111,11 +132,9 @@
         TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
         WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
 
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
-        log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK);
+        log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK, 1000L);
 
-        when(injectorSpy.currentTimeMillis()).thenReturn(1150L);
-        log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK);
+        log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK, 1150L);
 
         assertEquals("Wake Lock Log\n"
                 + "  01-01 00:00:01.000 - --- - ACQ UNKNOWN (partial)\n"
@@ -134,41 +153,33 @@
         WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
 
         // Wake lock 1 acquired - log size = 3
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
-        log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK);
+        log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK, 1000L);
 
         // Wake lock 2 acquired - log size = 3 + 3 = 6
-        when(injectorSpy.currentTimeMillis()).thenReturn(1150L);
-        log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK);
+        log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK, 1150L);
 
         // Wake lock 3 acquired - log size = 6 + 3 = 9
-        when(injectorSpy.currentTimeMillis()).thenReturn(1151L);
-        log.onWakeLockAcquired("TagThree", 101, PowerManager.PARTIAL_WAKE_LOCK);
+        log.onWakeLockAcquired("TagThree", 101, PowerManager.PARTIAL_WAKE_LOCK, 1151L);
 
         // We need more space - wake lock 1 acquisition is removed from the log and saved in the
         // list. Log size = 9 - 3 + 2 = 8
-        when(injectorSpy.currentTimeMillis()).thenReturn(1152L);
-        log.onWakeLockReleased("TagThree", 101);
+        log.onWakeLockReleased("TagThree", 101, 1152L);
 
         // We need more space - wake lock 2 acquisition is removed from the log and saved in the
         // list. Log size = 8 - 3 + 2 = 7
-        when(injectorSpy.currentTimeMillis()).thenReturn(1153L);
-        log.onWakeLockReleased("TagPartial", 101);
+        log.onWakeLockReleased("TagPartial", 101, 1153L);
 
         // We need more space - wake lock 3 acquisition is removed from the log and saved in the
         // list. Log size = 7 - 3 + 3 = 7
-        when(injectorSpy.currentTimeMillis()).thenReturn(1154L);
-        log.onWakeLockAcquired("TagFour", 101, PowerManager.PARTIAL_WAKE_LOCK);
+        log.onWakeLockAcquired("TagFour", 101, PowerManager.PARTIAL_WAKE_LOCK, 1154L);
 
         // We need more space - wake lock 3 release is removed from the log and wake lock 3
         // acquisition is removed from the list. Log size = 7 - 2 + 3 = 8
-        when(injectorSpy.currentTimeMillis()).thenReturn(1155L);
-        log.onWakeLockAcquired("TagFive", 101, PowerManager.PARTIAL_WAKE_LOCK);
+        log.onWakeLockAcquired("TagFive", 101, PowerManager.PARTIAL_WAKE_LOCK, 1155L);
 
         // We need more space - wake lock 1 release is removed from the log and wake lock 1
         // acquisition is removed from the list. Log size = 8 - 2 + 2 = 8
-        when(injectorSpy.currentTimeMillis()).thenReturn(1156L);
-        log.onWakeLockReleased("TagFull", 102);
+        log.onWakeLockReleased("TagFull", 102, 1156L);
 
         // Wake lock 2 acquisition is still printed because its release have not rolled off the log
         // yet.
@@ -191,8 +202,8 @@
         WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
 
         // Bad tag means it wont get written
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
-        log.onWakeLockAcquired(null /* tag */, 0 /* ownerUid */, PowerManager.PARTIAL_WAKE_LOCK);
+        log.onWakeLockAcquired(
+                null /* tag */, 0 /* ownerUid */, PowerManager.PARTIAL_WAKE_LOCK, 1000L);
 
         assertEquals("Wake Lock Log\n"
                 + "  -\n"
@@ -208,9 +219,8 @@
         TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
         WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
 
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
         log.onWakeLockAcquired("*job*/com.one.two.3hree/.one..Last", 101,
-                PowerManager.PARTIAL_WAKE_LOCK);
+                PowerManager.PARTIAL_WAKE_LOCK, 1000L);
 
         assertEquals("Wake Lock Log\n"
                 + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ "
@@ -228,10 +238,8 @@
         TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
         WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
 
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
-        log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK);
-        when(injectorSpy.currentTimeMillis()).thenReturn(1001L);
-        log.onWakeLockReleased("HowdyTag", 101);
+        log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK, 1000L);
+        log.onWakeLockReleased("HowdyTag", 101, 1001L);
 
         assertEquals("Wake Lock Log\n"
                 + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ HowdyTag (partial)\n"
@@ -250,12 +258,10 @@
         TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
         WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
 
-        when(injectorSpy.currentTimeMillis()).thenReturn(1100L);
-        log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK);
+        log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK, 1100L);
 
         // New element goes back in time...should not be written to log.
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
-        log.onWakeLockReleased("HowdyTag", 101);
+        log.onWakeLockReleased("HowdyTag", 101, 1000L);
 
         assertEquals("Wake Lock Log\n"
                 + "  01-01 00:00:01.100 - 101 (some.package1) - ACQ HowdyTag (partial)\n"
@@ -272,9 +278,8 @@
         TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
         WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
 
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
         log.onWakeLockAcquired("TagPartial", 101,
-                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.SYSTEM_WAKELOCK);
+                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.SYSTEM_WAKELOCK, 1000L);
 
         assertEquals("Wake Lock Log\n"
                         + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial "
@@ -293,9 +298,8 @@
         WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
 
         when(mPackageManager.getPackagesForUid(101)).thenReturn(null);
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
         log.onWakeLockAcquired("TagPartial", 101,
-                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE);
+                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, 1000L);
 
         assertEquals("Wake Lock Log\n"
                         + "  01-01 00:00:01.000 - 101 - ACQ TagPartial "
@@ -316,9 +320,8 @@
         when(mPackageManager.getPackagesForUid(101)).thenReturn(
                 new String[]{ "some.package1", "some.package2", "some.package3" });
 
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
         log.onWakeLockAcquired("TagPartial", 101,
-                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE);
+                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, 1000L);
 
         assertEquals("Wake Lock Log\n"
                         + "  01-01 00:00:01.000 - 101 (some.package1,...) - ACQ TagPartial "
@@ -336,17 +339,14 @@
         TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
         WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
 
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
         log.onWakeLockAcquired("TagPartial", 101,
-                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE);
+                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, 1000L);
 
-        when(injectorSpy.currentTimeMillis()).thenReturn(1150L);
         log.onWakeLockAcquired("TagFull", 101,
-                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, 1150L);
 
-        when(injectorSpy.currentTimeMillis()).thenReturn(1151L);
         log.onWakeLockAcquired("TagFull2", 101,
-                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, 1151L);
 
         assertEquals("Wake Lock Log\n"
                         + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial "
@@ -370,29 +370,23 @@
         TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
         WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
 
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
         log.onWakeLockAcquired("TagPartial", 101,
-                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE);
+                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, 1000L);
 
-        when(injectorSpy.currentTimeMillis()).thenReturn(1150L);
         log.onWakeLockAcquired("TagFull", 101,
-                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, 1150L);
 
-        when(injectorSpy.currentTimeMillis()).thenReturn(1151L);
         log.onWakeLockAcquired("TagFull2", 101,
-                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, 1151L);
 
-        when(injectorSpy.currentTimeMillis()).thenReturn(1152L);
         log.onWakeLockAcquired("TagFull3", 101,
-                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, 1152L);
 
-        when(injectorSpy.currentTimeMillis()).thenReturn(1153L);
         log.onWakeLockAcquired("TagFull4", 101,
-                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, 1153L);
 
-        when(injectorSpy.currentTimeMillis()).thenReturn(1154L);
         log.onWakeLockAcquired("TagFull5", 101,
-                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, 1154L);
 
         // The first 3 events have been removed from the log and they exist in the saved
         // acquisitions list. They should also use the cache when fetching the package names.
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java
index d29bf1a..3635e9a 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.os.BatteryManager;
@@ -49,9 +50,9 @@
     private static final int BATTERY_CHARGE_RATE_SECONDS_PER_LEVEL = 100;
 
     private MockClock mMockClock;
+    private BatteryStatsImpl.BatteryStatsConfig mConfig;
     private MockBatteryStatsImpl mBatteryStatsImpl;
 
-
     /**
      * Battery status. Must be one of the following:
      * {@link BatteryManager#BATTERY_STATUS_UNKNOWN}
@@ -91,8 +92,9 @@
 
     @Before
     public void setUp() throws IOException {
+        mConfig = mock(BatteryStatsImpl.BatteryStatsConfig.class);
         mMockClock = new MockClock();
-        mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock,
+        mBatteryStatsImpl = new MockBatteryStatsImpl(mConfig, mMockClock,
                 Files.createTempDirectory("BatteryStatsResetTest").toFile());
         mBatteryStatsImpl.onSystemReady(mock(Context.class));
 
@@ -110,10 +112,7 @@
 
     @Test
     public void testResetOnUnplug_highBatteryLevel() {
-        mBatteryStatsImpl.setBatteryStatsConfig(
-                new BatteryStatsImpl.BatteryStatsConfig.Builder()
-                        .setResetOnUnplugHighBatteryLevel(true)
-                        .build());
+        when(mConfig.shouldResetOnUnplugHighBatteryLevel()).thenReturn(true);
 
         long expectedResetTimeUs = 0;
 
@@ -137,10 +136,7 @@
         assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
 
         // disable high battery level reset on unplug.
-        mBatteryStatsImpl.setBatteryStatsConfig(
-                new BatteryStatsImpl.BatteryStatsConfig.Builder()
-                        .setResetOnUnplugHighBatteryLevel(false)
-                        .build());
+        when(mConfig.shouldResetOnUnplugHighBatteryLevel()).thenReturn(false);
 
         dischargeToLevel(60);
 
@@ -153,10 +149,7 @@
 
     @Test
     public void testResetOnUnplug_significantCharge() {
-        mBatteryStatsImpl.setBatteryStatsConfig(
-                new BatteryStatsImpl.BatteryStatsConfig.Builder()
-                        .setResetOnUnplugAfterSignificantCharge(true)
-                        .build());
+        when(mConfig.shouldResetOnUnplugAfterSignificantCharge()).thenReturn(true);
         long expectedResetTimeUs = 0;
 
         unplugBattery();
@@ -186,10 +179,7 @@
         assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
 
         // disable reset on unplug after significant charge.
-        mBatteryStatsImpl.setBatteryStatsConfig(
-                new BatteryStatsImpl.BatteryStatsConfig.Builder()
-                        .setResetOnUnplugAfterSignificantCharge(false)
-                        .build());
+        when(mConfig.shouldResetOnUnplugAfterSignificantCharge()).thenReturn(false);
 
         // Battery level dropped below 20%.
         dischargeToLevel(15);
@@ -256,11 +246,9 @@
     @Test
     public void testResetWhilePluggedIn_longPlugIn() {
         // disable high battery level reset on unplug.
-        mBatteryStatsImpl.setBatteryStatsConfig(
-                new BatteryStatsImpl.BatteryStatsConfig.Builder()
-                        .setResetOnUnplugHighBatteryLevel(false)
-                        .setResetOnUnplugAfterSignificantCharge(false)
-                        .build());
+        when(mConfig.shouldResetOnUnplugHighBatteryLevel()).thenReturn(false);
+        when(mConfig.shouldResetOnUnplugAfterSignificantCharge()).thenReturn(false);
+
         long expectedResetTimeUs = 0;
 
         plugBattery(BatteryManager.BATTERY_PLUGGED_USB);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 2d7cb22..6edfede 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -98,10 +98,12 @@
         mFreqsByPolicy.put(0, new int[]{300000, 1000000, 2000000});
         mFreqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000});
         mBatteryStatsConfigBuilder = new BatteryStatsImpl.BatteryStatsConfig.Builder()
-                .setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_CPU,
-                        10000)
-                .setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
-                        10000);
+                .setPowerStatsThrottlePeriodMillis(
+                        BatteryConsumer.powerComponentIdToString(
+                                BatteryConsumer.POWER_COMPONENT_CPU), 10000)
+                .setPowerStatsThrottlePeriodMillis(
+                        BatteryConsumer.powerComponentIdToString(
+                                BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO), 10000);
     }
 
     private void initBatteryStats() {
@@ -290,7 +292,8 @@
 
     public BatteryUsageStatsRule setPowerStatsThrottlePeriodMillis(int powerComponent,
             long throttleMs) {
-        mBatteryStatsConfigBuilder.setPowerStatsThrottlePeriodMillis(powerComponent, throttleMs);
+        mBatteryStatsConfigBuilder.setPowerStatsThrottlePeriodMillis(
+                BatteryConsumer.powerComponentIdToString(powerComponent), throttleMs);
         return this;
     }
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
index 4e3e80f..d1105a4 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
@@ -32,6 +32,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.IndentingPrintWriter;
 import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
@@ -51,6 +52,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.io.StringWriter;
 import java.util.function.IntSupplier;
 
 @RunWith(AndroidJUnit4.class)
@@ -127,6 +129,11 @@
         }
 
         @Override
+        public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+            return 0;
+        }
+
+        @Override
         public int getDefaultCpuPowerBrackets() {
             return mDefaultCpuPowerBrackets;
         }
@@ -363,6 +370,36 @@
                 .isEqualTo(528);
     }
 
+    @Test
+    public void dump() {
+        mockCpuScalingPolicies(1);
+        mockPowerProfile();
+        mockEnergyConsumers();
+
+        CpuPowerStatsCollector collector = createCollector(8, 0);
+        collector.collectStats();       // Establish baseline
+
+        mockKernelCpuStats(new long[]{1111, 2222, 3333},
+                new SparseArray<>() {{
+                    put(UID_1, new long[]{100, 200});
+                    put(UID_2, new long[]{100, 150});
+                    put(ISOLATED_UID, new long[]{200, 450});
+                }}, 0, 1234);
+
+        PowerStats powerStats = collector.collectStats();
+
+        StringWriter sw = new StringWriter();
+        IndentingPrintWriter pw = new IndentingPrintWriter(sw);
+        powerStats.dump(pw);
+        pw.flush();
+        String dump = sw.toString();
+
+        assertThat(dump).contains("duration=1234");
+        assertThat(dump).contains("steps: [1111, 2222, 3333]");
+        assertThat(dump).contains("UID 42: time: [100, 200]");
+        assertThat(dump).contains("UID 99: time: [300, 600]");
+    }
+
     private void mockCpuScalingPolicies(int clusterCount) {
         SparseArray<int[]> cpus = new SparseArray<>();
         SparseArray<int[]> freqs = new SparseArray<>();
@@ -386,8 +423,8 @@
     private CpuPowerStatsCollector createCollector(int defaultCpuPowerBrackets,
             int defaultCpuPowerBracketsPerEnergyConsumer) {
         CpuPowerStatsCollector collector = new CpuPowerStatsCollector(
-                new TestInjector(defaultCpuPowerBrackets, defaultCpuPowerBracketsPerEnergyConsumer),
-                0);
+                new TestInjector(defaultCpuPowerBrackets, defaultCpuPowerBracketsPerEnergyConsumer)
+        );
         collector.addConsumer(stats -> mCollectedStats = stats);
         collector.setEnabled(true);
         return collector;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
index 70c40f5..644ae47 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
@@ -25,6 +25,7 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.UserHandle;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -46,7 +47,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
@@ -92,9 +95,10 @@
         long duration = 0;
         long[] stats = null;
 
-        String[] cpuStatsDump = dumpCpuStats();
+        List<String> cpuStatsDump = dumpCpuStats();
         Pattern durationPattern = Pattern.compile("duration=([0-9]*)");
-        Pattern uidPattern = Pattern.compile("UID " + mTestPkgUid + ": \\[([0-9,\\s]*)]");
+        Pattern uidPattern = Pattern.compile(
+                "UID " + UserHandle.formatUid(mTestPkgUid) + ": time: [\\[]?([0-9,\\s]*)[]]?");
         for (String line : cpuStatsDump) {
             Matcher durationMatcher = durationPattern.matcher(line);
             if (durationMatcher.find()) {
@@ -119,15 +123,23 @@
         assertThat(total).isAtLeast((long) (WORK_DURATION_MS * 0.8));
     }
 
-    private String[] dumpCpuStats() throws Exception {
+    private List<String> dumpCpuStats() throws Exception {
+        ArrayList<String> cpuStats = new ArrayList<>();
         String dump = executeCmdSilent("dumpsys batterystats --sample");
         String[] lines = dump.split("\n");
+        boolean inCpuSection = false;
         for (int i = 0; i < lines.length; i++) {
-            if (lines[i].startsWith("CpuPowerStatsCollector")) {
-                return Arrays.copyOfRange(lines, i + 1, lines.length);
+            if (!inCpuSection) {
+                if (lines[i].startsWith("CpuPowerStatsCollector")) {
+                    inCpuSection = true;
+                }
+            } else if (lines[i].startsWith(" ")) {
+                cpuStats.add(lines[i]);
+            } else {
+                break;
             }
         }
-        return new String[0];
+        return cpuStats;
     }
 
     private void doSomeWork() throws Exception {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java
index f93c4da..0275319 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java
@@ -123,6 +123,11 @@
         }
 
         @Override
+        public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+            return 0;
+        }
+
+        @Override
         public PackageManager getPackageManager() {
             return mPackageManager;
         }
@@ -337,16 +342,18 @@
         pw.flush();
         String dump = sw.toString();
         assertThat(dump).contains("duration=100");
+        assertThat(dump).contains("sleep: 200 idle: 300 scan: 60000 call: 40000 energy: "
+                + ((64321 - 10000) * 1000 / 3500));
+        assertThat(dump).contains("(LTE) rx: 7000 tx: [8000, 9000, 1000, 2000, 3000]");
+        assertThat(dump).contains("(NR MMWAVE) rx: 6000 tx: [1000, 2000, 3000, 4000, 5000]");
         assertThat(dump).contains(
-                "stats=[200, 300, 60000, 40000, " + ((64321 - 10000) * 1000 / 3500) + ", 0, 0, 0]");
-        assertThat(dump).contains("state LTE: [7000, 8000, 9000, 1000, 2000, 3000]");
-        assertThat(dump).contains("state NR MMWAVE: [6000, 1000, 2000, 3000, 4000, 5000]");
-        assertThat(dump).contains("UID 24: [6000, 3000, 60, 30, 0]");
-        assertThat(dump).contains("UID 42: [1000, 2000, 100, 200, 0]");
+                "UID 24: rx-pkts: 60 rx-B: 6000 tx-pkts: 30 tx-B: 3000");
+        assertThat(dump).contains(
+                "UID 42: rx-pkts: 100 rx-B: 1000 tx-pkts: 200 tx-B: 2000");
     }
 
     private PowerStats collectPowerStats(boolean perNetworkTypeData) throws Throwable {
-        MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0);
+        MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector);
         collector.setEnabled(true);
 
         when(mConsumedEnergyRetriever.getEnergyConsumerIds(
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
index 4ac7ad8..29ef3b6 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
@@ -114,6 +114,11 @@
                 }
 
                 @Override
+                public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+                    return 0;
+                }
+
+                @Override
                 public PackageManager getPackageManager() {
                     return mPackageManager;
                 }
@@ -186,7 +191,7 @@
         aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
         aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
 
-        MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0);
+        MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector);
         collector.setEnabled(true);
 
         // Initial empty ModemActivityInfo.
@@ -305,79 +310,9 @@
     }
 
     @Test
-    public void measuredEnergyModel() {
-        // PowerStats hardware is available
-        when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.MOBILE_RADIO))
-                .thenReturn(new int[] {MOBILE_RADIO_ENERGY_CONSUMER_ID});
-
-        mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem")
-                .initMeasuredEnergyStatsLocked();
-
-        MobileRadioPowerStatsProcessor processor =
-                new MobileRadioPowerStatsProcessor(mStatsRule.getPowerProfile());
-
-        AggregatedPowerStatsConfig.PowerComponent config =
-                new AggregatedPowerStatsConfig.PowerComponent(
-                        BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
-                        .trackDeviceStates(STATE_POWER, STATE_SCREEN)
-                        .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
-                        .setProcessor(processor);
-
+    public void energyConsumerModel() {
         PowerComponentAggregatedPowerStats aggregatedStats =
-                new PowerComponentAggregatedPowerStats(
-                        new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config);
-
-        aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
-        aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
-        aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
-        aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
-
-        MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0);
-        collector.setEnabled(true);
-
-        // Initial empty ModemActivityInfo.
-        mockModemActivityInfo(new ModemActivityInfo(0L, 0L, 0L, new int[5], 0L));
-
-        when(mConsumedEnergyRetriever.getConsumedEnergyUws(
-                new int[]{MOBILE_RADIO_ENERGY_CONSUMER_ID}))
-                .thenReturn(new long[]{0});
-
-        // Establish a baseline
-        aggregatedStats.addPowerStats(collector.collectStats(), 0);
-
-        // Turn the screen off after 2.5 seconds
-        aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
-        aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
-        aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE,
-                5000);
-
-        // Note application network activity
-        NetworkStats networkStats = mockNetworkStats(10000, 1,
-                mockNetworkStatsEntry("cellular", APP_UID, 0, 0,
-                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 10000, 1500, 20000, 300, 100),
-                mockNetworkStatsEntry("cellular", APP_UID2, 0, 0,
-                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 5000, 500, 3000, 100, 111));
-
-        when(mNetworkStatsSupplier.get()).thenReturn(networkStats);
-
-        ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
-                new int[]{100, 200, 300, 400, 500}, 600);
-        mockModemActivityInfo(mai);
-
-        mStatsRule.setTime(10_000, 10_000);
-
-        long energyUws = 10_000_000L * VOLTAGE_MV / 1000L;
-        when(mConsumedEnergyRetriever.getConsumedEnergyUws(
-                new int[]{MOBILE_RADIO_ENERGY_CONSUMER_ID})).thenReturn(new long[]{energyUws});
-
-        when(mCallDurationSupplier.getAsLong()).thenReturn(200L);
-        when(mScanDurationSupplier.getAsLong()).thenReturn(5555L);
-
-        PowerStats powerStats = collector.collectStats();
-
-        aggregatedStats.addPowerStats(powerStats, 10_000);
-
-        processor.finish(aggregatedStats);
+                prepareAggregatedStats_energyConsumerModel();
 
         MobileRadioPowerStatsLayout statsLayout =
                 new MobileRadioPowerStatsLayout(
@@ -448,6 +383,102 @@
                 .isWithin(PRECISION).of(0.75);
     }
 
+    @Test
+    public void test_toString() {
+        PowerComponentAggregatedPowerStats stats = prepareAggregatedStats_energyConsumerModel();
+        String string = stats.toString();
+        assertThat(string).contains("(pwr-other scr-on)"
+                + " sleep: 500 idle: 750 scan: 1388 call: 50 energy: 2500000 power: 0.672");
+        assertThat(string).contains("(pwr-other scr-other)"
+                + " sleep: 1500 idle: 2250 scan: 4166 call: 150 energy: 7500000 power: 2.02");
+        assertThat(string).contains("(pwr-other scr-on other)"
+                + " rx: 150 tx: [25, 50, 75, 100, 125]");
+        assertThat(string).contains("(pwr-other scr-other other)"
+                + " rx: 450 tx: [75, 150, 225, 300, 375]");
+        assertThat(string).contains("(pwr-other scr-on fg)"
+                + " rx-pkts: 375 rx-B: 2500 tx-pkts: 75 tx-B: 5000 power: 0.198");
+        assertThat(string).contains("(pwr-other scr-other bg)"
+                + " rx-pkts: 375 rx-B: 2500 tx-pkts: 75 tx-B: 5000 power: 0.198");
+        assertThat(string).contains("(pwr-other scr-other fgs)"
+                + " rx-pkts: 750 rx-B: 5000 tx-pkts: 150 tx-B: 10000 power: 0.396");
+    }
+
+    private PowerComponentAggregatedPowerStats prepareAggregatedStats_energyConsumerModel() {
+        // PowerStats hardware is available
+        when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.MOBILE_RADIO))
+                .thenReturn(new int[] {MOBILE_RADIO_ENERGY_CONSUMER_ID});
+
+        mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem")
+                .initMeasuredEnergyStatsLocked();
+
+        MobileRadioPowerStatsProcessor processor =
+                new MobileRadioPowerStatsProcessor(mStatsRule.getPowerProfile());
+
+        AggregatedPowerStatsConfig.PowerComponent config =
+                new AggregatedPowerStatsConfig.PowerComponent(
+                        BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
+                        .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+                        .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
+                        .setProcessor(processor);
+
+        PowerComponentAggregatedPowerStats aggregatedStats =
+                new PowerComponentAggregatedPowerStats(
+                        new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config);
+
+        aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
+        aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
+        aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
+        aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
+
+        MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector);
+        collector.setEnabled(true);
+
+        // Initial empty ModemActivityInfo.
+        mockModemActivityInfo(new ModemActivityInfo(0L, 0L, 0L, new int[5], 0L));
+
+        when(mConsumedEnergyRetriever.getConsumedEnergyUws(
+                new int[]{MOBILE_RADIO_ENERGY_CONSUMER_ID}))
+                .thenReturn(new long[]{0});
+
+        // Establish a baseline
+        aggregatedStats.addPowerStats(collector.collectStats(), 0);
+
+        // Turn the screen off after 2.5 seconds
+        aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
+        aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
+        aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE,
+                5000);
+
+        // Note application network activity
+        NetworkStats networkStats = mockNetworkStats(10000, 1,
+                mockNetworkStatsEntry("cellular", APP_UID, 0, 0,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 10000, 1500, 20000, 300, 100),
+                mockNetworkStatsEntry("cellular", APP_UID2, 0, 0,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 5000, 500, 3000, 100, 111));
+
+        when(mNetworkStatsSupplier.get()).thenReturn(networkStats);
+
+        ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
+                new int[]{100, 200, 300, 400, 500}, 600);
+        mockModemActivityInfo(mai);
+
+        mStatsRule.setTime(10_000, 10_000);
+
+        long energyUws = 10_000_000L * VOLTAGE_MV / 1000L;
+        when(mConsumedEnergyRetriever.getConsumedEnergyUws(
+                new int[]{MOBILE_RADIO_ENERGY_CONSUMER_ID})).thenReturn(new long[]{energyUws});
+
+        when(mCallDurationSupplier.getAsLong()).thenReturn(200L);
+        when(mScanDurationSupplier.getAsLong()).thenReturn(5555L);
+
+        PowerStats powerStats = collector.collectStats();
+
+        aggregatedStats.addPowerStats(powerStats, 10_000);
+
+        processor.finish(aggregatedStats);
+        return aggregatedStats;
+    }
+
     private int[] states(int... states) {
         return states;
     }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index 1d48975..2c03f9d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -72,6 +72,11 @@
         this(DEFAULT_CONFIG, clock, historyDirectory, handler, new PowerStatsUidResolver());
     }
 
+    MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, File historyDirectory) {
+        this(config, clock, historyDirectory, new Handler(Looper.getMainLooper()),
+                new PowerStatsUidResolver());
+    }
+
     MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, File historyDirectory,
             Handler handler, PowerStatsUidResolver powerStatsUidResolver) {
         super(config, clock, new MonotonicClock(0, clock), historyDirectory, handler,
@@ -137,13 +142,6 @@
         return MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS;
     }
 
-    public MockBatteryStatsImpl setBatteryStatsConfig(BatteryStatsConfig config) {
-        synchronized (this) {
-            mBatteryStatsConfig = config;
-        }
-        return this;
-    }
-
     public MockBatteryStatsImpl setNetworkStats(NetworkStats networkStats) {
         mNetworkStats = networkStats;
         return this;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
index 1b045c5..ae258cd3 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
@@ -29,8 +29,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.PrintWriter;
-import java.io.StringWriter;
 import java.util.Arrays;
 
 @RunWith(AndroidJUnit4.class)
@@ -165,7 +163,7 @@
     }
 
     @Test
-    public void dump() {
+    public void test_toString() {
         MultiStateStats.Factory factory = makeFactory(true, true, false);
         MultiStateStats multiStateStats = factory.create();
         multiStateStats.setState(0 /* batteryState */, 0 /* off */, 1000);
@@ -175,13 +173,10 @@
         multiStateStats.setState(1 /* procState */, BatteryConsumer.PROCESS_STATE_BACKGROUND, 3000);
         multiStateStats.increment(new long[]{100, 200}, 5000);
 
-        StringWriter sw = new StringWriter();
-        PrintWriter pw = new PrintWriter(sw, true);
-        multiStateStats.dump(pw, Arrays::toString);
-        assertThat(sw.toString()).isEqualTo(
-                "plugged-in fg [25, 50]\n"
-                + "on-battery fg [25, 50]\n"
-                + "on-battery bg [50, 100]\n"
+        assertThat(multiStateStats.toString()).isEqualTo(
+                "(plugged-in fg) [25, 50]\n"
+                + "(on-battery fg) [25, 50]\n"
+                + "(on-battery bg) [50, 100]"
         );
     }
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
index dadcf3f..69d655b 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
@@ -98,6 +98,11 @@
                 }
 
                 @Override
+                public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+                    return 0;
+                }
+
+                @Override
                 public PackageManager getPackageManager() {
                     return mPackageManager;
                 }
@@ -175,7 +180,7 @@
         aggregatedPowerStats.setDeviceState(STATE_POWER, POWER_STATE_OTHER, 0);
         aggregatedPowerStats.setDeviceState(STATE_SCREEN, SCREEN_STATE_ON, 0);
 
-        MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0);
+        MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector);
         collector.setEnabled(true);
 
         // Initial empty ModemActivityInfo.
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java
index 8b1d423..a280cfe 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java
@@ -138,6 +138,11 @@
         }
 
         @Override
+        public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+            return 0;
+        }
+
+        @Override
         public PackageManager getPackageManager() {
             return mPackageManager;
         }
@@ -304,16 +309,20 @@
         String dump = sw.toString();
         assertThat(dump).contains("duration=7500");
         assertThat(dump).contains(
-                "stats=[6000, 1000, 300, 200, 634, 945, " + ((64321 - 10000) * 1000 / 3500)
-                        + ", 0, 0]");
-        assertThat(dump).contains("UID 24: [6000, 3000, 60, 30, 400, 600, 0]");
-        assertThat(dump).contains("UID 42: [1000, 2000, 100, 200, 234, 345, 0]");
+                "rx: 6000 tx: 1000 idle: 300 scan: 200 basic-scan: 634 batched-scan: 945"
+                        + " energy: " + ((64321 - 10000) * 1000 / 3500));
+        assertThat(dump).contains(
+                "UID 24: rx-pkts: 60 rx-B: 6000 tx-pkts: 30 tx-B: 3000"
+                        + " scan: 400 batched-scan: 600");
+        assertThat(dump).contains(
+                "UID 42: rx-pkts: 100 rx-B: 1000 tx-pkts: 200 tx-B: 2000"
+                        + " scan: 234 batched-scan: 345");
     }
 
     private PowerStats collectPowerStats(boolean hasPowerReporting) {
         when(mWifiManager.isEnhancedPowerReportingSupported()).thenReturn(hasPowerReporting);
 
-        WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0);
+        WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector);
         collector.setEnabled(true);
 
         when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.WIFI))
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
index 257a1a6..3ceaf35 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
@@ -142,6 +142,11 @@
                 }
 
                 @Override
+                public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+                    return 0;
+                }
+
+                @Override
                 public PackageManager getPackageManager() {
                     return mPackageManager;
                 }
@@ -195,7 +200,7 @@
 
         PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor);
 
-        WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0);
+        WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector);
         collector.setEnabled(true);
 
         // Initial empty WifiActivityEnergyInfo.
@@ -307,7 +312,7 @@
 
         PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor);
 
-        WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0);
+        WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector);
         collector.setEnabled(true);
 
         // Initial empty WifiActivityEnergyInfo.
@@ -420,7 +425,7 @@
 
         PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor);
 
-        WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0);
+        WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector);
         collector.setEnabled(true);
 
         // Establish a baseline
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index 27c522d..b56af87 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -25,6 +25,13 @@
                 value="/data/local/tmp/cts/content/broken_shortcut.xml" />
     </target_preparer>
 
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="force-skip-system-props" value="true" />
+        <option name="set-global-setting" key="verifier_engprod" value="1" />
+        <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
+        <option name="restore-settings" value="true" />
+    </target_preparer>
+
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="install-arg" value="-t" />
diff --git a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
index 88ca029..ec78bce 100644
--- a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
@@ -112,7 +112,7 @@
         resources.addOverride(
                 com.android.internal.R.array.config_defaultPinnerServiceFiles, new String[0]);
         resources.addOverride(com.android.internal.R.bool.config_pinnerCameraApp, false);
-        resources.addOverride(com.android.internal.R.bool.config_pinnerHomeApp, false);
+        resources.addOverride(com.android.internal.R.integer.config_pinnerHomePinBytes, 0);
         resources.addOverride(com.android.internal.R.bool.config_pinnerAssistantApp, false);
 
         mFakeDeviceConfigInterface = new FakeDeviceConfigInterface();
@@ -242,7 +242,7 @@
     public void testPinHomeApp() throws Exception {
         // Enable HOME app pinning
         mContext.getOrCreateTestableResources()
-                .addOverride(com.android.internal.R.bool.config_pinnerHomeApp, true);
+                .addOverride(com.android.internal.R.integer.config_pinnerHomePinBytes, 1024);
         PinnerService pinnerService = new PinnerService(mContext, mInjector);
         pinnerService.onStart();
 
@@ -266,7 +266,7 @@
     public void testPinHomeAppOnBootCompleted() throws Exception {
         // Enable HOME app pinning
         mContext.getOrCreateTestableResources()
-                .addOverride(com.android.internal.R.bool.config_pinnerHomeApp, true);
+                .addOverride(com.android.internal.R.integer.config_pinnerHomePinBytes, 1024);
         PinnerService pinnerService = new PinnerService(mContext, mInjector);
         pinnerService.onStart();
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 16d05b1..6e6d5a8 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -89,6 +89,7 @@
 import android.os.Process;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
+import android.os.test.FakePermissionEnforcer;
 import android.util.Pair;
 import android.view.Display;
 import android.view.KeyEvent;
@@ -181,6 +182,7 @@
     @Mock private FingerprintGestureDispatcher mMockFingerprintGestureDispatcher;
     @Mock private MagnificationProcessor mMockMagnificationProcessor;
     @Mock private RemoteCallback.OnResultListener mMockListener;
+    FakePermissionEnforcer mFakePermissionEnforcer = new FakePermissionEnforcer();
 
     @Before
     public void setup() {
@@ -198,6 +200,8 @@
         PowerManager powerManager =
                 new PowerManager(mMockContext, mMockIPowerManager, mMockIThermalService, mHandler);
         when(mMockContext.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
+        when(mMockContext.getSystemService(Context.PERMISSION_ENFORCER_SERVICE))
+                .thenReturn(mFakePermissionEnforcer);
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockPackageManager.hasSystemFeature(FEATURE_FINGERPRINT)).thenReturn(true);
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index cb4fc75..ca15aa2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -30,7 +30,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -42,6 +44,7 @@
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -102,6 +105,7 @@
 import com.android.internal.accessibility.util.AccessibilityUtils;
 import com.android.internal.accessibility.util.ShortcutUtils;
 import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.content.PackageMonitor;
 import com.android.server.LocalServices;
 import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener;
 import com.android.server.accessibility.magnification.FullScreenMagnificationController;
@@ -1620,6 +1624,67 @@
                 .containsExactlyElementsIn(Set.of(daltonizerTile));
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_MANAGER_PACKAGE_MONITOR_LOGIC_FIX)
+    public void onHandleForceStop_dontDoIt_packageEnabled_returnsTrue() {
+        setupShortcutTargetServices();
+        AccessibilityUserState userState = mA11yms.getCurrentUserState();
+        userState.mEnabledServices.addAll(
+                userState.mInstalledServices.stream().map(
+                        (AccessibilityServiceInfo::getComponentName)).toList());
+        String[] packages = userState.mEnabledServices.stream().map(
+                ComponentName::getPackageName).toList().toArray(new String[0]);
+
+        PackageMonitor monitor = spy(mA11yms.getPackageMonitor());
+        when(monitor.getChangingUserId()).thenReturn(UserHandle.USER_SYSTEM);
+        mA11yms.setPackageMonitor(monitor);
+
+        assertTrue(mA11yms.getPackageMonitor().onHandleForceStop(
+                new Intent(),
+                packages,
+                UserHandle.USER_SYSTEM,
+                false
+        ));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MANAGER_PACKAGE_MONITOR_LOGIC_FIX)
+    public void onHandleForceStop_doIt_packageEnabled_returnsFalse() {
+        setupShortcutTargetServices();
+        AccessibilityUserState userState = mA11yms.getCurrentUserState();
+        userState.mEnabledServices.addAll(
+                userState.mInstalledServices.stream().map(
+                        (AccessibilityServiceInfo::getComponentName)).toList());
+        String[] packages = userState.mEnabledServices.stream().map(
+                ComponentName::getPackageName).toList().toArray(new String[0]);
+
+        PackageMonitor monitor = spy(mA11yms.getPackageMonitor());
+        when(monitor.getChangingUserId()).thenReturn(UserHandle.USER_SYSTEM);
+        mA11yms.setPackageMonitor(monitor);
+
+        assertFalse(mA11yms.getPackageMonitor().onHandleForceStop(
+                new Intent(),
+                packages,
+                UserHandle.USER_SYSTEM,
+                true
+        ));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MANAGER_PACKAGE_MONITOR_LOGIC_FIX)
+    public void onHandleForceStop_dontDoIt_packageNotEnabled_returnsFalse() {
+        PackageMonitor monitor = spy(mA11yms.getPackageMonitor());
+        when(monitor.getChangingUserId()).thenReturn(UserHandle.USER_SYSTEM);
+        mA11yms.setPackageMonitor(monitor);
+
+        assertFalse(mA11yms.getPackageMonitor().onHandleForceStop(
+                new Intent(),
+                new String[]{ "FOO", "BAR"},
+                UserHandle.USER_SYSTEM,
+                false
+        ));
+    }
+
     private static AccessibilityServiceInfo mockAccessibilityServiceInfo(
             ComponentName componentName) {
         return mockAccessibilityServiceInfo(
@@ -1630,7 +1695,7 @@
             ComponentName componentName,
             boolean isSystemApp, boolean isAlwaysOnService) {
         AccessibilityServiceInfo accessibilityServiceInfo =
-                Mockito.spy(new AccessibilityServiceInfo());
+                spy(new AccessibilityServiceInfo());
         accessibilityServiceInfo.setComponentName(componentName);
         ResolveInfo mockResolveInfo = Mockito.mock(ResolveInfo.class);
         when(accessibilityServiceInfo.getResolveInfo()).thenReturn(mockResolveInfo);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index cda8b01..c4946f0 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -26,7 +26,6 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.notNull;
-import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -56,6 +55,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.test.FakePermissionEnforcer;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -82,7 +82,6 @@
 import java.util.HashSet;
 import java.util.List;
 
-
 /**
  * Tests for AccessibilityServiceConnection
  */
@@ -130,6 +129,7 @@
     IBrailleDisplayController mMockBrailleDisplayController;
     @Mock
     MotionEventInjector mMockMotionEventInjector;
+    FakePermissionEnforcer mFakePermissionEnforcer = new FakePermissionEnforcer();
 
     MessageCapturingHandler mHandler = new MessageCapturingHandler(null);
 
@@ -151,12 +151,14 @@
         when(mMockA11yTrace.isA11yTracingEnabled()).thenReturn(false);
         when(mMockContext.getSystemService(Context.DISPLAY_SERVICE))
                 .thenReturn(new DisplayManager(mMockContext));
+        when(mMockContext.getSystemService(Context.PERMISSION_ENFORCER_SERVICE))
+                .thenReturn(mFakePermissionEnforcer);
 
         mConnection = new AccessibilityServiceConnection(mMockUserState, mMockContext,
-                COMPONENT_NAME, mServiceInfo, SERVICE_ID, mHandler, new Object(),
-                mMockSecurityPolicy, mMockSystemSupport, mMockA11yTrace,
-                mMockWindowManagerInternal, mMockSystemActionPerformer,
-                mMockA11yWindowManager, mMockActivityTaskManagerInternal);
+                        COMPONENT_NAME, mServiceInfo, SERVICE_ID, mHandler, new Object(),
+                        mMockSecurityPolicy, mMockSystemSupport, mMockA11yTrace,
+                        mMockWindowManagerInternal, mMockSystemActionPerformer,
+                        mMockA11yWindowManager, mMockActivityTaskManagerInternal);
         when(mMockSecurityPolicy.canPerformGestures(mConnection)).thenReturn(true);
         when(mMockSecurityPolicy.checkAccessibilityAccess(mConnection)).thenReturn(true);
     }
@@ -317,6 +319,8 @@
     @Test
     @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_BRAILLE_DISPLAY_HID)
     public void connectBluetoothBrailleDisplay() throws Exception {
+        mFakePermissionEnforcer.grant(Manifest.permission.BLUETOOTH_CONNECT);
+        mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
         final String macAddress = "00:11:22:33:AA:BB";
         final byte[] descriptor = {0x05, 0x41};
         Bundle bd = new Bundle();
@@ -338,9 +342,6 @@
     @Test
     @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_BRAILLE_DISPLAY_HID)
     public void connectBluetoothBrailleDisplay_throwsForMissingBluetoothConnectPermission() {
-        doThrow(SecurityException.class).when(mMockContext)
-                .enforceCallingPermission(eq(Manifest.permission.BLUETOOTH_CONNECT), any());
-
         assertThrows(SecurityException.class,
                 () -> mConnection.connectBluetoothBrailleDisplay("unused",
                         mMockBrailleDisplayController));
@@ -349,6 +350,7 @@
     @Test
     @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_BRAILLE_DISPLAY_HID)
     public void connectBluetoothBrailleDisplay_throwsForNullMacAddress() {
+        mFakePermissionEnforcer.grant(Manifest.permission.BLUETOOTH_CONNECT);
         assertThrows(NullPointerException.class,
                 () -> mConnection.connectBluetoothBrailleDisplay(null,
                         mMockBrailleDisplayController));
@@ -357,6 +359,7 @@
     @Test
     @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_BRAILLE_DISPLAY_HID)
     public void connectBluetoothBrailleDisplay_throwsForMisformattedMacAddress() {
+        mFakePermissionEnforcer.grant(Manifest.permission.BLUETOOTH_CONNECT);
         assertThrows(IllegalArgumentException.class,
                 () -> mConnection.connectBluetoothBrailleDisplay("12:34",
                         mMockBrailleDisplayController));
@@ -365,6 +368,7 @@
     @Test
     @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_BRAILLE_DISPLAY_HID)
     public void connectUsbBrailleDisplay() throws Exception {
+        mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
         final String serialNumber = "myUsbDevice";
         final byte[] descriptor = {0x05, 0x41};
         Bundle bd = new Bundle();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java
index 3d0db71..c0e5f76 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java
@@ -38,6 +38,7 @@
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.os.Handler;
+import android.os.test.FakePermissionEnforcer;
 import android.view.accessibility.AccessibilityEvent;
 
 import com.android.server.wm.WindowManagerInternal;
@@ -79,6 +80,7 @@
     AccessibilityTrace mMockA11yTrace;
     @Mock
     WindowManagerInternal mMockWindowManagerInternal;
+    FakePermissionEnforcer mFakePermissionEnforcer  = new FakePermissionEnforcer();
     ProxyAccessibilityServiceConnection mProxyConnection;
     AccessibilityServiceInfo mAccessibilityServiceInfo;
     private int mFocusStrokeWidthDefaultValue;
@@ -90,6 +92,8 @@
         MockitoAnnotations.initMocks(this);
         when(mMockContext.getResources()).thenReturn(resources);
         when(mMockSecurityPolicy.checkAccessibilityAccess(any())).thenReturn(true);
+        when(mMockContext.getSystemService(Context.PERMISSION_ENFORCER_SERVICE))
+                .thenReturn(mFakePermissionEnforcer);
 
         mAccessibilityServiceInfo = new AccessibilityServiceInfo();
         mProxyConnection = new ProxyAccessibilityServiceConnection(mMockContext, COMPONENT_NAME,
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
index f1b356a..52b33db 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
@@ -50,6 +50,7 @@
 import android.os.IBinder;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.test.FakePermissionEnforcer;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -113,6 +114,7 @@
     @Mock private IBinder mMockServiceAsBinder;
     @Mock private VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal;
     @Mock private IVirtualDeviceManager mMockIVirtualDeviceManager;
+    FakePermissionEnforcer mFakePermissionEnforcer  = new FakePermissionEnforcer();
 
     private int mFocusStrokeWidthDefaultValue;
     private int mFocusColorDefaultValue;
@@ -132,6 +134,8 @@
         when(mMockContext.getMainExecutor())
                 .thenReturn(InstrumentationRegistry.getTargetContext().getMainExecutor());
 
+        when(mMockContext.getSystemService(Context.PERMISSION_ENFORCER_SERVICE))
+                .thenReturn(mFakePermissionEnforcer);
         when(mMockVirtualDeviceManagerInternal.getDeviceIdsForUid(anyInt())).thenReturn(
                 new ArraySet(Set.of(DEVICE_ID)));
         LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
index 95a1f5a..e24592e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
@@ -39,6 +39,7 @@
 import android.content.pm.ServiceInfo;
 import android.hardware.display.DisplayManager;
 import android.os.IBinder;
+import android.os.test.FakePermissionEnforcer;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.view.WindowManager;
@@ -80,12 +81,15 @@
     @Mock IBinder mMockOwner;
     @Mock IAccessibilityServiceClient mMockAccessibilityServiceClient;
     @Mock IBinder mMockServiceAsBinder;
+    FakePermissionEnforcer mFakePermissionEnforcer  = new FakePermissionEnforcer();
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
         when(mMockSystemSupport.getKeyEventDispatcher()).thenReturn(mock(KeyEventDispatcher.class));
+        when(mMockContext.getSystemService(Context.PERMISSION_ENFORCER_SERVICE))
+                .thenReturn(mFakePermissionEnforcer);
 
         when(mMockServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo);
         mMockResolveInfo.serviceInfo = mock(ServiceInfo.class);
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
new file mode 100644
index 0000000..7f2327aa
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.server.appop;
+
+import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
+import android.content.AttributionSource;
+import android.os.Process;
+import android.permission.PermissionManager;
+import android.permission.flags.Flags;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.testing.TestableContext;
+import android.virtualdevice.cts.common.VirtualDeviceRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Objects;
+
+@RunWith(AndroidJUnit4.class)
+public class AppOpsDeviceAwareServiceTest {
+
+    @Rule
+    public final TestableContext mContext =
+            new TestableContext(InstrumentationRegistry.getInstrumentation().getTargetContext());
+
+    @Rule
+    public VirtualDeviceRule virtualDeviceRule =
+            VirtualDeviceRule.withAdditionalPermissions(
+                    Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+                    Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+                    Manifest.permission.CREATE_VIRTUAL_DEVICE,
+                    Manifest.permission.GET_APP_OPS_STATS);
+
+    private static final String ATTRIBUTION_TAG_1 = "attributionTag1";
+    private static final String ATTRIBUTION_TAG_2 = "attributionTag2";
+    private final AppOpsManager mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+    private final PermissionManager mPermissionManager =
+            mContext.getSystemService(PermissionManager.class);
+
+    private VirtualDeviceManager.VirtualDevice mVirtualDevice;
+
+    @Before
+    public void setUp() {
+        mVirtualDevice =
+                virtualDeviceRule.createManagedVirtualDevice(
+                        new VirtualDeviceParams.Builder()
+                                .setDevicePolicy(POLICY_TYPE_CAMERA, DEVICE_POLICY_CUSTOM)
+                                .build());
+
+        mPermissionManager.grantRuntimePermission(
+                mContext.getOpPackageName(),
+                Manifest.permission.CAMERA,
+                mVirtualDevice.getPersistentDeviceId());
+
+        mPermissionManager.grantRuntimePermission(
+                mContext.getOpPackageName(),
+                Manifest.permission.CAMERA,
+                VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+    }
+
+    @RequiresFlagsEnabled(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED)
+    @Test
+    public void noteProxyOp_proxyAppOnDefaultDevice() {
+        AppOpsManager.OpEventProxyInfo proxyInfo =
+                noteProxyOpWithDeviceId(TestableContext.DEVICE_ID_DEFAULT);
+        assertThat(proxyInfo.getDeviceId())
+                .isEqualTo(VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+    }
+
+    @RequiresFlagsEnabled(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED)
+    @Test
+    public void noteProxyOp_proxyAppOnRemoteDevice() {
+        AppOpsManager.OpEventProxyInfo proxyInfo =
+                noteProxyOpWithDeviceId(mVirtualDevice.getDeviceId());
+        assertThat(proxyInfo.getDeviceId()).isEqualTo(mVirtualDevice.getPersistentDeviceId());
+    }
+
+    private AppOpsManager.OpEventProxyInfo noteProxyOpWithDeviceId(int proxyAppDeviceId) {
+        AttributionSource proxiedAttributionSource =
+                new AttributionSource.Builder(Process.myUid())
+                        .setPackageName(mContext.getOpPackageName())
+                        .setAttributionTag(ATTRIBUTION_TAG_2)
+                        .setDeviceId(mVirtualDevice.getDeviceId())
+                        .build();
+
+        AttributionSource proxyAttributionSource =
+                new AttributionSource.Builder(Process.myUid())
+                        .setPackageName(mContext.getOpPackageName())
+                        .setAttributionTag(ATTRIBUTION_TAG_1)
+                        .setDeviceId(proxyAppDeviceId)
+                        .setNextAttributionSource(proxiedAttributionSource)
+                        .build();
+
+        int mode = mAppOpsManager.noteProxyOp(OP_CAMERA, proxyAttributionSource, null, false);
+        assertThat(mode).isEqualTo(AppOpsManager.MODE_ALLOWED);
+
+        List<AppOpsManager.PackageOps> packagesOps =
+                mAppOpsManager.getPackagesForOps(
+                        new String[] {AppOpsManager.OPSTR_CAMERA},
+                        mVirtualDevice.getPersistentDeviceId());
+
+        AppOpsManager.PackageOps packageOps =
+                packagesOps.stream()
+                        .filter(
+                                pkg ->
+                                        Objects.equals(
+                                                pkg.getPackageName(), mContext.getOpPackageName()))
+                        .findFirst()
+                        .orElseThrow();
+
+        AppOpsManager.OpEntry opEntry =
+                packageOps.getOps().stream()
+                        .filter(op -> op.getOp() == OP_CAMERA)
+                        .findFirst()
+                        .orElseThrow();
+
+        return opEntry.getLastProxyInfo(OP_FLAGS_ALL_TRUSTED);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/autofill/SaveEventLoggerTest.java b/services/tests/servicestests/src/com/android/server/autofill/SaveEventLoggerTest.java
index 0bca59d..9e52078 100644
--- a/services/tests/servicestests/src/com/android/server/autofill/SaveEventLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/autofill/SaveEventLoggerTest.java
@@ -55,31 +55,4 @@
         assertThat(latencySaveFinishMillis.getValue())
                 .isNotEqualTo(SaveEventLogger.UNINITIATED_TIMESTAMP);
     }
-
-    @Test
-    public void testTimestampsNotInitialized() {
-        SaveEventLogger mLogger =
-                spy(SaveEventLogger.forSessionId(1, SaveEventLogger.UNINITIATED_TIMESTAMP));
-
-        mLogger.maybeSetLatencySaveUiDisplayMillis();
-        mLogger.maybeSetLatencySaveRequestMillis();
-        mLogger.maybeSetLatencySaveFinishMillis();
-        ArgumentCaptor<Long> latencySaveUiDisplayMillis = ArgumentCaptor.forClass(Long.class);
-        ArgumentCaptor<Long> latencySaveRequestMillis = ArgumentCaptor.forClass(Long.class);
-        ArgumentCaptor<Long> latencySaveFinishMillis = ArgumentCaptor.forClass(Long.class);
-
-        verify(mLogger, times(1))
-                .maybeSetLatencySaveUiDisplayMillis(latencySaveUiDisplayMillis.capture());
-        verify(mLogger, times(1))
-                .maybeSetLatencySaveRequestMillis(latencySaveRequestMillis.capture());
-        verify(mLogger, times(1))
-                .maybeSetLatencySaveFinishMillis(latencySaveFinishMillis.capture());
-
-        assertThat(latencySaveUiDisplayMillis.getValue())
-                .isEqualTo(SaveEventLogger.UNINITIATED_TIMESTAMP);
-        assertThat(latencySaveRequestMillis.getValue())
-                .isEqualTo(SaveEventLogger.UNINITIATED_TIMESTAMP);
-        assertThat(latencySaveFinishMillis.getValue())
-                .isEqualTo(SaveEventLogger.UNINITIATED_TIMESTAMP);
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 3dc375c..9cd3186 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -368,24 +368,32 @@
     }
 
     @Test
-    public void testAuthenticate_throwsWhenUsingTestConfigurations() {
+    public void testAuthenticate_throwsWhenUsingTestApis() {
         final PromptInfo promptInfo = mock(PromptInfo.class);
-        when(promptInfo.containsPrivateApiConfigurations()).thenReturn(false);
-        when(promptInfo.containsTestConfigurations()).thenReturn(true);
+        when(promptInfo.requiresInternalPermission()).thenReturn(false);
+        when(promptInfo.requiresTestOrInternalPermission()).thenReturn(true);
 
-        testAuthenticate_throwsWhenUsingTestConfigurations(promptInfo);
+        testAuthenticate_throwsSecurityException(promptInfo);
     }
 
     @Test
     public void testAuthenticate_throwsWhenUsingPrivateApis() {
         final PromptInfo promptInfo = mock(PromptInfo.class);
-        when(promptInfo.containsPrivateApiConfigurations()).thenReturn(true);
-        when(promptInfo.containsTestConfigurations()).thenReturn(false);
+        when(promptInfo.requiresInternalPermission()).thenReturn(true);
+        when(promptInfo.requiresTestOrInternalPermission()).thenReturn(false);
 
-        testAuthenticate_throwsWhenUsingTestConfigurations(promptInfo);
+        testAuthenticate_throwsSecurityException(promptInfo);
     }
 
-    private void testAuthenticate_throwsWhenUsingTestConfigurations(PromptInfo promptInfo) {
+    @Test
+    public void testAuthenticate_throwsWhenUsingAdvancedApis() {
+        final PromptInfo promptInfo = mock(PromptInfo.class);
+        when(promptInfo.requiresAdvancedPermission()).thenReturn(true);
+
+        testAuthenticate_throwsSecurityException(promptInfo);
+    }
+
+    private void testAuthenticate_throwsSecurityException(PromptInfo promptInfo) {
         mAuthService = new AuthService(mContext, mInjector);
         mAuthService.onStart();
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java
new file mode 100644
index 0000000..3698d6f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.server.biometrics;
+
+import static com.android.server.biometrics.sensors.BiometricNotificationUtils.NOTIFICATION_ID;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+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 android.app.NotificationManager;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BiometricDanglingReceiverTest {
+    @Rule
+    public MockitoRule mockitoRule = MockitoJUnit.rule();
+
+    private BiometricDanglingReceiver mBiometricDanglingReceiver;
+
+    @Rule
+    public final TestableContext mContext = spy(new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null));
+
+    @Mock
+    NotificationManager mNotificationManager;
+
+    @Mock
+    Intent mIntent;
+
+    @Captor
+    private ArgumentCaptor<Intent> mArgumentCaptor;
+
+    @Before
+    public void setUp() {
+        mContext.addMockSystemService(NotificationManager.class, mNotificationManager);
+    }
+
+    @Test
+    public void testFingerprintRegisterReceiver() {
+        initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FINGERPRINT);
+        verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any(),
+                eq(Context.RECEIVER_NOT_EXPORTED));
+    }
+
+    @Test
+    public void testFaceRegisterReceiver() {
+        initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FACE);
+        verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any(),
+                eq(Context.RECEIVER_NOT_EXPORTED));
+    }
+
+    @Test
+    public void testOnReceive_fingerprintReEnrollLaunch() {
+        initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FINGERPRINT);
+        when(mIntent.getAction()).thenReturn(
+                BiometricDanglingReceiver.ACTION_FINGERPRINT_RE_ENROLL_LAUNCH);
+
+        mBiometricDanglingReceiver.onReceive(mContext, mIntent);
+
+        // Verify fingerprint enroll process is launched.
+        verify(mContext).startActivity(mArgumentCaptor.capture());
+        assertThat(mArgumentCaptor.getValue().getAction())
+                .isEqualTo(Settings.ACTION_FINGERPRINT_ENROLL);
+
+        // Verify notification is canceled
+        verify(mNotificationManager).cancelAsUser("FingerprintReEnroll", NOTIFICATION_ID,
+                UserHandle.CURRENT);
+
+        // Verify receiver is unregistered after receiving the broadcast
+        verify(mContext).unregisterReceiver(mBiometricDanglingReceiver);
+    }
+
+    @Test
+    public void testOnReceive_faceReEnrollLaunch() {
+        initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FACE);
+        when(mIntent.getAction()).thenReturn(
+                BiometricDanglingReceiver.ACTION_FACE_RE_ENROLL_LAUNCH);
+
+        mBiometricDanglingReceiver.onReceive(mContext, mIntent);
+
+        // Verify face enroll process is launched.
+        verify(mContext).startActivity(mArgumentCaptor.capture());
+        assertThat(mArgumentCaptor.getValue().getAction())
+                .isEqualTo(BiometricDanglingReceiver.FACE_SETTINGS_ACTION);
+
+        // Verify notification is canceled
+        verify(mNotificationManager).cancelAsUser("FaceReEnroll", NOTIFICATION_ID,
+                UserHandle.CURRENT);
+
+        // Verify receiver is unregistered after receiving the broadcast.
+        verify(mContext).unregisterReceiver(mBiometricDanglingReceiver);
+    }
+
+    private void initBroadcastReceiver(int modality) {
+        mBiometricDanglingReceiver = new BiometricDanglingReceiver(mContext, modality);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index fc573d2..3789531 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -1228,6 +1228,11 @@
             Slog.d(TAG, "TestInternalEnumerateClient#startHalOperation");
             onEnumerationResult(TEST_FINGERPRINT, 0 /* remaining */);
         }
+
+        @Override
+        protected int getModality() {
+            return BiometricsProtoEnums.MODALITY_FINGERPRINT;
+        }
     }
 
     private static class TestRemovalClient extends RemovalClient<Fingerprint, Object> {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java
index 9845b58..d8bdd50 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java
@@ -20,8 +20,10 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -79,15 +81,21 @@
     private final int mBiometricId = 1;
     private final Face mFace = new Face("face", mBiometricId, 1 /* deviceId */);
     private FaceInternalEnumerateClient mClient;
+    private boolean mNotificationSent;
 
     @Before
     public void setUp() {
         when(mAidlSession.getSession()).thenReturn(mSession);
-
         final List<Face> enrolled = new ArrayList<>();
         enrolled.add(mFace);
-        mClient = new FaceInternalEnumerateClient(mContext, () -> mAidlSession, mToken, USER_ID,
-                TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger, mBiometricContext);
+        mClient = spy(new FaceInternalEnumerateClient(mContext, () -> mAidlSession, mToken, USER_ID,
+                TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger, mBiometricContext));
+
+        mNotificationSent = false;
+        doAnswer(invocation -> {
+            mNotificationSent = true;
+            return null;
+        }).when(mClient).sendDanglingNotification(anyList());
     }
 
     @Test
@@ -101,6 +109,7 @@
 
         verify(mSession).enumerateEnrollments();
         assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+        assertThat(mNotificationSent).isFalse();
         verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
         verify(mCallback).onClientFinished(mClient, true);
     }
@@ -116,6 +125,7 @@
 
         verify(mSession).enumerateEnrollments();
         assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+        assertThat(mNotificationSent).isFalse();
         verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
         verify(mCallback, never()).onClientFinished(mClient, true);
     }
@@ -131,6 +141,7 @@
 
         verify(mSession).enumerateEnrollments();
         assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+        assertThat(mNotificationSent).isTrue();
         verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId);
         verify(mCallback).onClientFinished(mClient, true);
     }
@@ -147,6 +158,7 @@
 
         verify(mSession).enumerateEnrollments();
         assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1);
+        assertThat(mNotificationSent).isFalse();
         verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
         verify(mCallback, never()).onClientFinished(mClient, true);
     }
@@ -164,6 +176,7 @@
 
         verify(mSession).enumerateEnrollments();
         assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1);
+        assertThat(mNotificationSent).isTrue();
         verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId);
         verify(mCallback).onClientFinished(mClient, true);
     }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java
index b5df836..fab1200 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java
@@ -20,8 +20,10 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -80,15 +82,23 @@
 
     private FingerprintInternalEnumerateClient mClient;
 
+    private boolean mNotificationSent;
+
     @Before
     public void setUp() {
         when(mAidlSession.getSession()).thenReturn(mSession);
 
         List<Fingerprint> enrolled = new ArrayList<>();
         enrolled.add(new Fingerprint("one", 1, 1));
-        mClient = new FingerprintInternalEnumerateClient(mContext, () -> mAidlSession, mToken,
+        mClient = spy(new FingerprintInternalEnumerateClient(mContext, () -> mAidlSession, mToken,
                 USER_ID, TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger,
-                mBiometricContext);
+                mBiometricContext));
+
+        mNotificationSent = false;
+        doAnswer(invocation -> {
+            mNotificationSent = true;
+            return null;
+        }).when(mClient).sendDanglingNotification(anyList());
     }
 
     @Test
@@ -104,6 +114,7 @@
         assertThat(mClient.getUnknownHALTemplates().stream()
                 .flatMap(x -> Stream.of(x.getBiometricId()))
                 .collect(Collectors.toList())).containsExactly(2, 3);
+        assertThat(mNotificationSent).isTrue();
         verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, 1);
         verify(mCallback).onClientFinished(mClient, true);
     }
@@ -118,6 +129,7 @@
 
         verify(mSession).enumerateEnrollments();
         assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+        assertThat(mNotificationSent).isFalse();
         verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
         verify(mCallback).onClientFinished(mClient, true);
     }
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index fd880dd..178e7ec 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -33,7 +33,6 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.DisplayInfo;
@@ -41,13 +40,11 @@
 
 import androidx.test.InstrumentationRegistry;
 
-import com.android.input.flags.Flags;
 import com.android.server.LocalServices;
 import com.android.server.input.InputManagerInternal;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -60,9 +57,6 @@
     private static final String LANGUAGE_TAG = "en-US";
     private static final String LAYOUT_TYPE = "qwerty";
 
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
     @Mock
     private InputManagerInternal mInputManagerInternalMock;
     @Mock
@@ -77,8 +71,6 @@
 
     @Before
     public void setUp() throws Exception {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER);
-
         MockitoAnnotations.initMocks(this);
         mInputManagerMockHelper = new InputManagerMockHelper(
                 TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
index b33a8aa..00c8ed1 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
@@ -53,7 +53,7 @@
     private IInputDevicesChangedListener mDevicesChangedListener;
     private final Map<String /* uniqueId */, Integer /* displayId */> mDisplayIdMapping =
             new HashMap<>();
-    private final Map<String /* phys */, String /* uniqueId */> mUniqueIdAssociation =
+    private final Map<String /* phys */, String /* uniqueId */> mUniqueIdAssociationByPort =
             new HashMap<>();
 
     InputManagerMockHelper(TestableLooper testableLooper,
@@ -79,11 +79,11 @@
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
         doAnswer(inv -> mDevices.get(inv.getArgument(0)))
                 .when(mIInputManagerMock).getInputDevice(anyInt());
-        doAnswer(inv -> mUniqueIdAssociation.put(inv.getArgument(0),
-                inv.getArgument(1))).when(mIInputManagerMock).addUniqueIdAssociation(
+        doAnswer(inv -> mUniqueIdAssociationByPort.put(inv.getArgument(0),
+                inv.getArgument(1))).when(mIInputManagerMock).addUniqueIdAssociationByPort(
                         anyString(), anyString());
-        doAnswer(inv -> mUniqueIdAssociation.remove(inv.getArgument(0))).when(
-                mIInputManagerMock).removeUniqueIdAssociation(anyString());
+        doAnswer(inv -> mUniqueIdAssociationByPort.remove(inv.getArgument(0))).when(
+                mIInputManagerMock).removeUniqueIdAssociationByPort(anyString());
 
         // Set a new instance of InputManager for testing that uses the IInputManager mock as the
         // interface to the server.
@@ -113,7 +113,7 @@
                 .setDescriptor(phys)
                 .setExternal(true)
                 .setAssociatedDisplayId(
-                        mDisplayIdMapping.getOrDefault(mUniqueIdAssociation.get(phys),
+                        mDisplayIdMapping.getOrDefault(mUniqueIdAssociationByPort.get(phys),
                                 Display.INVALID_DISPLAY))
                 .build();
 
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 2b81d78..da8961d 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -339,8 +339,6 @@
         LocalServices.removeServiceForTest(DisplayManagerInternal.class);
         LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
 
-        mSetFlagsRule.enableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER);
-
         doNothing().when(mInputManagerInternalMock)
                 .setMousePointerAccelerationEnabled(anyBoolean(), anyInt());
         doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt());
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 855c658..b4cc343 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -441,11 +441,6 @@
         @Override
         public void runCryptoSelfTest() {}
 
-        @Override
-        public String[] getPersonalAppsForSuspension(int userId) {
-            return new String[]{};
-        }
-
         public void setSystemCurrentTimeMillis(long value) {
             mCurrentTimeMillis = value;
         }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
index be5e262..c1ae852 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
@@ -24,8 +24,10 @@
 
 import static org.mockito.Mockito.spy;
 
+import android.annotation.RequiresPermission;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
 import android.os.Looper;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
@@ -72,6 +74,11 @@
             protected void writeStringSystemProperty(String key, String value) {
                 // do nothing
             }
+
+            @Override
+            protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+                // do nothing
+            }
         };
 
         Looper looper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
index 5be3c8e..a5f7bb1 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
@@ -22,8 +22,10 @@
 
 import static org.mockito.Mockito.spy;
 
+import android.annotation.RequiresPermission;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 import android.os.Looper;
 import android.os.test.TestLooper;
@@ -84,6 +86,11 @@
                     protected Looper getServiceLooper() {
                         return mTestLooper.getLooper();
                     }
+
+                    @Override
+                    protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+                        // do nothing
+                    }
                 };
 
         mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(hdmiControlService) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index 7845c30..857ee1a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -22,8 +22,10 @@
 
 import static org.mockito.Mockito.spy;
 
+import android.annotation.RequiresPermission;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
@@ -90,6 +92,11 @@
                     protected Looper getServiceLooper() {
                         return mTestLooper.getLooper();
                     }
+
+                    @Override
+                    protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+                        // do nothing
+                    }
                 };
 
         Looper looper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
index 98789ac..6ace9f1 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
@@ -33,8 +33,10 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.RequiresPermission;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
 import android.hardware.hdmi.DeviceFeatures;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
@@ -140,17 +142,8 @@
                         // do nothing
                     }
 
-                    /**
-                     * Override displayOsd to prevent it from broadcasting an intent, which
-                     * can trigger a SecurityException.
-                     */
                     @Override
-                    void displayOsd(int messageId) {
-                        // do nothing
-                    }
-
-                    @Override
-                    void displayOsd(int messageId, int extra) {
+                    protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
                         // do nothing
                     }
                 };
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
index 9b65762..2dd593c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
@@ -18,6 +18,8 @@
 import static org.junit.Assert.assertEquals;
 
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 import android.os.Looper;
@@ -103,6 +105,11 @@
                     protected Looper getServiceLooper() {
                         return mTestLooper.getLooper();
                     }
+
+                    @Override
+                    protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+                        // do nothing
+                    }
                 };
         mHdmiCecLocalDeviceAudioSystem =
                 new HdmiCecLocalDeviceAudioSystem(hdmiControlService) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
index 922706e..e669e7c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
@@ -25,8 +25,10 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.RequiresPermission;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
@@ -90,6 +92,11 @@
             protected void writeStringSystemProperty(String key, String value) {
                 // do nothing
             }
+
+            @Override
+            protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+                // do nothing
+            }
         };
 
         Looper looper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
index 68ef80f..29d20a6 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
@@ -30,7 +30,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.annotation.RequiresPermission;
 import android.content.Context;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
@@ -123,6 +125,11 @@
                     boolean isPowerStandby() {
                         return false;
                     }
+
+                    @Override
+                    protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+                        // do nothing
+                    }
                 };
 
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
index 26b448a..d32b75b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
@@ -29,7 +29,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.annotation.RequiresPermission;
 import android.content.Context;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiPortInfo;
@@ -132,6 +134,11 @@
                     boolean isPowerStandby() {
                         return false;
                     }
+
+                    @Override
+                    protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+                        // do nothing
+                    }
                 };
 
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
index a621055..c7574bd 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -36,6 +36,7 @@
 
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiPortInfo;
@@ -98,6 +99,8 @@
                 audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()));
         doNothing().when(mHdmiControlServiceSpy)
                 .writeStringSystemProperty(anyString(), anyString());
+        doNothing().when(mHdmiControlServiceSpy)
+                .sendBroadcastAsUser(any(Intent.class));
         doReturn(mHdmiCecAtomWriterSpy).when(mHdmiControlServiceSpy).getAtomWriter();
         HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
         doReturn(hdmiCecConfig).when(mHdmiControlServiceSpy).getHdmiCecConfig();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java
index 30ce961..e1e101f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java
@@ -19,6 +19,7 @@
 import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_UNKNOWN;
 import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -30,6 +31,7 @@
 
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiPortInfo;
@@ -90,6 +92,8 @@
                 audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()));
         doNothing().when(mHdmiControlServiceSpy)
                 .writeStringSystemProperty(anyString(), anyString());
+        doNothing().when(mHdmiControlServiceSpy)
+                .sendBroadcastAsUser(any(Intent.class));
         doReturn(mHdmiCecAtomWriterSpy).when(mHdmiControlServiceSpy).getAtomWriter();
 
         HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
index 0870bac..7ed596e 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
@@ -48,6 +48,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 
+import android.content.Intent;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 import android.os.Binder;
@@ -110,6 +111,8 @@
         doAnswer(__ -> mCecVersion).when(mHdmiControlServiceSpy).getCecVersion();
         doNothing().when(mHdmiControlServiceSpy)
                 .writeStringSystemProperty(anyString(), anyString());
+        doNothing().when(mHdmiControlServiceSpy)
+                .sendBroadcastAsUser(any(Intent.class));
         mHdmiControlServiceSpy.setDeviceConfig(new FakeDeviceConfigWrapper());
 
         mNativeWrapper = new FakeNativeWrapper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 5520897..5502de8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -26,7 +26,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.annotation.RequiresPermission;
 import android.content.Context;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiPortInfo;
@@ -114,6 +116,11 @@
                             return defVal;
                     }
                 }
+
+                @Override
+                protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+                    // do nothing
+                }
             };
 
         mHdmiControlService.getHdmiCecConfig().setIntValue(
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 28da97c..8df7d54 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -28,7 +28,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.annotation.RequiresPermission;
 import android.content.Context;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiPortInfo;
@@ -138,6 +140,11 @@
                     boolean canGoToStandby() {
                         return true;
                     }
+
+                    @Override
+                    protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+                        // do nothing
+                    }
                 };
 
         mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index 3dd8312..192be2a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -37,7 +37,9 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.RequiresPermission;
 import android.content.Context;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiPortInfo;
 import android.hardware.tv.cec.V1_0.Result;
@@ -169,6 +171,11 @@
                     void wakeUp() {
                         mWakeupMessageReceived = true;
                     }
+
+                    @Override
+                    protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+                        // do nothing
+                    }
                 };
         mHdmiControlService.setIoLooper(mTestLooper.getLooper());
         mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 4faeea5..b50684b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -26,6 +26,8 @@
 import static com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE;
+import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF;
+import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -41,7 +43,9 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.RequiresPermission;
 import android.content.Context;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiPortInfo;
@@ -184,17 +188,8 @@
                         return mEarcBlocksArc;
                     }
 
-                    /**
-                     * Override displayOsd to prevent it from broadcasting an intent, which
-                     * can trigger a SecurityException.
-                    */
                     @Override
-                    void displayOsd(int messageId) {
-                        // do nothing
-                    }
-
-                    @Override
-                    void displayOsd(int messageId, int extra) {
+                    protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
                         // do nothing
                     }
                 };
@@ -1787,9 +1782,17 @@
         HdmiCecMessage activeSourceFromTv =
                 HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
 
-        mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
+        // Go to standby to invalidate the active source on the local device s.t. the
+        // RequestActiveSourceAction will start.
+        mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+
         mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
-        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+
+        // Skip the LauncherX API timeout.
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
         mTestLooper.dispatchAll();
 
         assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1798,6 +1801,10 @@
         mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
         mTestLooper.dispatchAll();
 
+        // Assume there was a retry and the action did not finish earlier.
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
     }
 
@@ -1807,9 +1814,18 @@
                 HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
         HdmiCecMessage activeSourceFromTv =
                 HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
-        mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
+
+        // Go to standby to invalidate the active source on the local device s.t. the
+        // RequestActiveSourceAction will start.
+        mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+
         mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
-        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+
+        // Skip the LauncherX API timeout.
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
         mTestLooper.dispatchAll();
 
         assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1834,8 +1850,18 @@
                 HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
         HdmiCecMessage activeSourceFromTv =
                 HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
-        mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
-        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+
+        // Go to standby to invalidate the active source on the local device s.t. the
+        // RequestActiveSourceAction will start.
+        mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+
+        mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
+        mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+
+        // Skip the LauncherX API timeout.
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
         mTestLooper.dispatchAll();
 
         assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1854,8 +1880,16 @@
                 HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
         HdmiCecMessage activeSourceFromTv =
                 HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
-        mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
-        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+
+        // Go to standby to invalidate the active source on the local device s.t. the
+        // RequestActiveSourceAction will start.
+        mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+
+        mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
+        mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+
         HdmiDeviceInfo playbackDevice = HdmiDeviceInfo.cecDeviceBuilder()
                 .setLogicalAddress(ADDR_PLAYBACK_1)
                 .setPhysicalAddress(0x1000)
@@ -1869,6 +1903,10 @@
         mHdmiControlService.getHdmiCecNetwork().addCecDevice(playbackDevice);
         mTestLooper.dispatchAll();
 
+        // Skip the LauncherX API timeout.
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
+        mTestLooper.dispatchAll();
+
         assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
         mNativeWrapper.clearResultMessages();
         mHdmiCecLocalDeviceTv.deviceSelect(playbackDevice.getId(), null);
@@ -1881,6 +1919,41 @@
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
     }
 
+    @Test
+    public void onAddressAllocated_sendSourceChangingMessage_noRequestActiveSourceMessage() {
+        HdmiCecMessage requestActiveSource =
+                HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
+        HdmiCecMessage activeSourceFromTv =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+        HdmiCecMessage setStreamPathFromTv =
+                HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2000);
+
+        // Go to standby to invalidate the active source on the local device s.t. the
+        // RequestActiveSourceAction will start.
+        mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+
+        mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+
+        // Even if the device at the end of this path doesn't answer to this message, TV should not
+        // continue the RequestActiveSourceAction.
+        mHdmiControlService.sendCecCommand(setStreamPathFromTv);
+
+        // Skip the LauncherX API timeout.
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestActiveSource);
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        // Assume there was a retry and the action did not finish earlier.
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
+    }
 
     @Test
     public void newDeviceConnectedIfOnlyOneGiveOsdNameSent() {
@@ -1907,7 +1980,12 @@
         HdmiCecMessage activeSourceFromTv =
                 HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
 
-        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_WAKE_UP_MESSAGE);
+        // Go to standby to invalidate the active source on the local device s.t. the
+        // TV will send <Active Source> when it selects its internal source.
+        mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+
+        mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
         mTestLooper.dispatchAll();
 
         mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
@@ -1937,6 +2015,8 @@
     public void handleStandby_fromActiveSource_standby() {
         mPowerManager.setInteractive(true);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+
         mHdmiControlService.setActiveSource(ADDR_PLAYBACK_1, 0x1000,
                 "HdmiCecLocalDeviceTvTest");
         mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
index c002067..9412ee0 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
@@ -21,8 +21,10 @@
 
 import static org.mockito.Mockito.spy;
 
+import android.annotation.RequiresPermission;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiPortInfo;
@@ -88,6 +90,11 @@
             boolean isPowerStandby() {
                 return false;
             }
+
+            @Override
+            protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+                // do nothing
+            }
         };
         mHdmiControlService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 9a92c70..126a658 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -52,6 +52,7 @@
 
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiPortInfo;
@@ -106,6 +107,7 @@
     private HdmiPortInfo[] mHdmiPortInfo;
     private ArrayList<Integer> mLocalDeviceTypes = new ArrayList<>();
     private static final int PORT_ID_EARC_SUPPORTED = 3;
+    private static final int EARC_TRIGGER_START_ARC_ACTION_DELAY = 500;
 
     @Before
     public void setUp() throws Exception {
@@ -120,6 +122,8 @@
                 audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()));
         doNothing().when(mHdmiControlServiceSpy)
                 .writeStringSystemProperty(anyString(), anyString());
+        doNothing().when(mHdmiControlServiceSpy)
+                .sendBroadcastAsUser(any(Intent.class));
 
         mMyLooper = mTestLooper.getLooper();
 
@@ -1374,6 +1378,11 @@
                 PORT_ID_EARC_SUPPORTED);
         verify(mHdmiControlServiceSpy, times(1))
                 .notifyEarcStatusToAudioService(eq(false), eq(new ArrayList<>()));
+        // ARC should be never initiated here. It should be started after 500 ms.
+        verify(mHdmiControlServiceSpy, times(0)).startArcAction(anyBoolean(), any());
+        // We move 500 ms forward because the action is only started 500 ms later.
+        mTestLooper.moveTimeForward(EARC_TRIGGER_START_ARC_ACTION_DELAY);
+        mTestLooper.dispatchAll();
         verify(mHdmiControlServiceSpy, times(1)).startArcAction(eq(true), any());
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index 4641802..298ff46 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -26,8 +26,10 @@
 
 import static org.mockito.Mockito.spy;
 
+import android.annotation.RequiresPermission;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
@@ -104,6 +106,11 @@
             protected void writeStringSystemProperty(String key, String value) {
                 // do nothing
             }
+
+            @Override
+            protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+                // do nothing
+            }
         };
 
         Looper looper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
index 9f0a44c..1d4a72f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
@@ -25,8 +25,10 @@
 
 import static org.mockito.Mockito.spy;
 
+import android.annotation.RequiresPermission;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiPortInfo;
@@ -78,6 +80,11 @@
             protected void writeStringSystemProperty(String key, String value) {
                 // do nothing
             }
+
+            @Override
+            protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+                // do nothing
+            }
         };
 
         Looper looper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
index 043db1e..cafe1e7 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
@@ -21,7 +21,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.annotation.RequiresPermission;
 import android.content.Context;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.os.Looper;
@@ -115,6 +117,11 @@
                     boolean isPowerStandbyOrTransient() {
                         return false;
                     }
+
+                    @Override
+                    protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+                        // do nothing
+                    }
                 };
 
         mHdmiControlService.setIoLooper(mMyLooper);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java
index 061e1f9..864a182 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java
@@ -21,7 +21,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.annotation.RequiresPermission;
 import android.content.Context;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
@@ -72,6 +74,11 @@
             protected void writeStringSystemProperty(String key, String value) {
                 // do nothing
             }
+
+            @Override
+            protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+                // do nothing
+            }
         };
         mIsPowerStandby = false;
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java
index b25ea2c..06709cd 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java
@@ -21,7 +21,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.annotation.RequiresPermission;
 import android.content.Context;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 import android.os.Looper;
@@ -75,6 +77,11 @@
             boolean verifyPhysicalAddresses(HdmiCecMessage message) {
                 return true;
             }
+
+            @Override
+            protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+                // do nothing
+            }
         };
 
         mMyLooper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
index f608c235..5163e29 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
@@ -29,7 +29,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.annotation.RequiresPermission;
 import android.content.Context;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiPortInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
@@ -177,6 +179,11 @@
                     protected HdmiCecConfig getHdmiCecConfig() {
                         return hdmiCecConfig;
                     }
+
+                    @Override
+                    protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+                        // do nothing
+                    }
                 };
 
         mHdmiControlService.setIoLooper(mMyLooper);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
index a73f4aa..e4297ef 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
@@ -24,12 +24,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.spy;
 
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
 import android.hardware.hdmi.DeviceFeatures;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
@@ -87,6 +89,8 @@
                 audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()));
         doNothing().when(mHdmiControlServiceSpy)
                 .writeStringSystemProperty(anyString(), anyString());
+        doNothing().when(mHdmiControlServiceSpy)
+                .sendBroadcastAsUser(any(Intent.class));
 
         mLooper = mTestLooper.getLooper();
         mHdmiControlServiceSpy.setIoLooper(mLooper);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
index 02bed22..4dcc6a4 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
@@ -25,8 +25,10 @@
 
 import static org.mockito.Mockito.spy;
 
+import android.annotation.RequiresPermission;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiPortInfo;
 import android.os.Looper;
@@ -82,17 +84,8 @@
                 // do nothing
             }
 
-	    /**
-             * Override displayOsd to prevent it from broadcasting an intent, which
-             * can trigger a SecurityException.
-             */
             @Override
-            void displayOsd(int messageId) {
-                // do nothing
-            }
-
-            @Override
-            void displayOsd(int messageId, int extra) {
+            protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
                 // do nothing
             }
         };
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
index df27e78..4aa074b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
@@ -23,7 +23,9 @@
 import static org.junit.Assert.assertTrue;
 
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.content.Context;
+import android.content.Intent;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 import android.os.Looper;
@@ -143,6 +145,11 @@
                     protected boolean isStandbyMessageReceived() {
                         return mStandbyMessageReceived;
                     }
+
+                    @Override
+                    protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
+                        // do nothing
+                    }
                 };
 
         Looper looper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
index 40ecaf1..7dd1847 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -42,6 +42,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.HandlerThread;
 import android.os.LocaleList;
 import android.os.Process;
@@ -488,7 +489,7 @@
 
         setUpPackageInstalled(pkgNameA);
 
-        mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID);
+        mPackageMonitor.onPackageAddedWithExtras(pkgNameA, DEFAULT_UID, new Bundle());
 
         verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
                 LocaleList.forLanguageTags(langTagsA), false, FrameworkStatsLog
@@ -504,7 +505,7 @@
 
         setUpPackageInstalled(pkgNameB);
 
-        mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID);
+        mPackageMonitor.onPackageAddedWithExtras(pkgNameB, DEFAULT_UID, new Bundle());
 
         verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
                 LocaleList.forLanguageTags(langTagsB), true, FrameworkStatsLog
@@ -518,6 +519,66 @@
     }
 
     @Test
+    public void testRestore_appInstalledAfterSUW_restoresFromStage_ArchiveEnabled()
+            throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        HashMap<String, LocalesInfo> pkgLocalesMap = new HashMap<>();
+        String pkgNameA = "com.android.myAppA";
+        String pkgNameB = "com.android.myAppB";
+        String langTagsA = "ru";
+        String langTagsB = "hi,fr";
+        LocalesInfo localesInfoA = new LocalesInfo(langTagsA, false);
+        LocalesInfo localesInfoB = new LocalesInfo(langTagsB, true);
+        pkgLocalesMap.put(pkgNameA, localesInfoA);
+        pkgLocalesMap.put(pkgNameB, localesInfoB);
+        writeTestPayload(out, pkgLocalesMap);
+        setUpPackageNotInstalled(pkgNameA);
+        setUpPackageNotInstalled(pkgNameB);
+        setUpLocalesForPackage(pkgNameA, LocaleList.getEmptyLocaleList());
+        setUpLocalesForPackage(pkgNameB, LocaleList.getEmptyLocaleList());
+        setUpPackageNamesForSp(new ArraySet<>());
+
+        Bundle bundle = new Bundle();
+        bundle.putBoolean(Intent.EXTRA_ARCHIVAL, true);
+        mPackageMonitor.onPackageAddedWithExtras(pkgNameA, DEFAULT_UID, bundle);
+        mPackageMonitor.onPackageAddedWithExtras(pkgNameB, DEFAULT_UID, bundle);
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        verifyNothingRestored();
+
+        setUpPackageInstalled(pkgNameA);
+
+        mPackageMonitor.onPackageUpdateFinished(pkgNameA, DEFAULT_UID);
+
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
+                LocaleList.forLanguageTags(langTagsA), false, FrameworkStatsLog
+                .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
+
+        mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameA, false, false);
+
+        verify(mMockSpEditor, times(0)).putStringSet(anyString(), any());
+
+        pkgLocalesMap.remove(pkgNameA);
+
+        verifyStageDataForUser(pkgLocalesMap, DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
+
+        setUpPackageInstalled(pkgNameB);
+
+        mPackageMonitor.onPackageUpdateFinished(pkgNameB, DEFAULT_UID);
+
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
+                LocaleList.forLanguageTags(langTagsB), true, FrameworkStatsLog
+                .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
+
+        mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameB, true, false);
+
+        verify(mMockSpEditor, times(1)).putStringSet(Integer.toString(DEFAULT_USER_ID),
+            new ArraySet<>(Arrays.asList(pkgNameB)));
+        checkStageDataDoesNotExist(DEFAULT_USER_ID);
+    }
+
+    @Test
     public void testRestore_appInstalledAfterSUWAndLocalesAlreadySet_restoresNothing()
             throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -535,7 +596,7 @@
         setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
         setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.forLanguageTags("hi,mr"));
 
-        mPackageMonitor.onPackageAdded(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        mPackageMonitor.onPackageAddedWithExtras(DEFAULT_PACKAGE_NAME, DEFAULT_UID, new Bundle());
 
         // Since locales are already set, we should not restore anything for it.
         verifyNothingRestored();
@@ -612,7 +673,7 @@
                 DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.minusHours(1).toMillis());
         setUpPackageInstalled(pkgNameA);
 
-        mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID);
+        mPackageMonitor.onPackageAddedWithExtras(pkgNameA, DEFAULT_UID, new Bundle());
 
         verify(mMockLocaleManagerService, times(1)).setApplicationLocales(
                 pkgNameA, DEFAULT_USER_ID, LocaleList.forLanguageTags(langTagsA), false,
@@ -627,7 +688,7 @@
                 DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.plusSeconds(1).toMillis());
         setUpPackageInstalled(pkgNameB);
 
-        mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID);
+        mPackageMonitor.onPackageAddedWithExtras(pkgNameB, DEFAULT_UID, new Bundle());
 
         verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameB), anyInt(),
                 any(), anyBoolean(), anyInt());
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index 80fb5e3..1514de0 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -89,6 +89,7 @@
     private static final String WRAPPING_KEY_ALIAS = "KeySyncTaskTest/WrappingKey";
     private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
     private static final int TEST_USER_ID = 1000;
+    private static final int TEST_USER_ID_2 = 1002;
     private static final int TEST_RECOVERY_AGENT_UID = 10009;
     private static final int TEST_RECOVERY_AGENT_UID2 = 10010;
     private static final byte[] TEST_VAULT_HANDLE =
@@ -824,6 +825,48 @@
     }
 
     @Test
+    public void run_unlock_keepsRemoteLskfVerificationCounter() throws Exception {
+        mRecoverableKeyStoreDb.setBadRemoteGuessCounter(TEST_USER_ID, 5);
+        mRecoverableKeyStoreDb.setBadRemoteGuessCounter(TEST_USER_ID_2, 4);
+        mKeySyncTask = new KeySyncTask(
+          mRecoverableKeyStoreDb,
+          mRecoverySnapshotStorage,
+          mSnapshotListenersStorage,
+          TEST_USER_ID,
+          CREDENTIAL_TYPE_PIN,
+          "12345".getBytes(),
+          /*credentialUpdated=*/ false,
+          mPlatformKeyManager,
+          mTestOnlyInsecureCertificateHelper,
+          mMockScrypt);
+        mKeySyncTask.run();
+
+        assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(TEST_USER_ID)).isEqualTo(5);
+        assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(TEST_USER_ID_2)).isEqualTo(4);
+    }
+
+    @Test
+    public void run_secretChange_resetsRemoteLskfVerificationCounter() throws Exception {
+        mRecoverableKeyStoreDb.setBadRemoteGuessCounter(TEST_USER_ID, 5);
+        mRecoverableKeyStoreDb.setBadRemoteGuessCounter(TEST_USER_ID_2, 4);
+        mKeySyncTask = new KeySyncTask(
+          mRecoverableKeyStoreDb,
+          mRecoverySnapshotStorage,
+          mSnapshotListenersStorage,
+          TEST_USER_ID,
+          CREDENTIAL_TYPE_PIN,
+          "12345".getBytes(),
+          /*credentialUpdated=*/ true,
+          mPlatformKeyManager,
+          mTestOnlyInsecureCertificateHelper,
+          mMockScrypt);
+        mKeySyncTask.run();
+
+        assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(TEST_USER_ID)).isEqualTo(0);
+        assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(TEST_USER_ID_2)).isEqualTo(4);
+    }
+
+    @Test
     public void run_customLockScreen_RecoveryStatusFailure() throws Exception {
       mKeySyncTask = new KeySyncTask(
           mRecoverableKeyStoreDb,
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
index 07fb9fc..570256b 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
@@ -19,9 +19,16 @@
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DEFAULT;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 import static android.util.DebugUtils.valueToString;
 
 import static org.junit.Assert.assertEquals;
@@ -51,7 +58,10 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.test.FakePermissionEnforcer;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.ArrayMap;
 
 import androidx.test.filters.SmallTest;
@@ -62,6 +72,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -84,6 +95,9 @@
     @Mock private IBatteryStats.Stub mBatteryStatsService;
     @Mock private INetd.Stub mNetdService;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
     private static final int TEST_UID = 111;
 
     @NonNull
@@ -254,6 +268,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_USE_METERED_FIREWALL_CHAINS)
     public void testMeteredNetworkRestrictions() throws RemoteException {
         // Make sure the mocked netd method returns true.
         doReturn(true).when(mNetdService).bandwidthEnableDataSaver(anyBoolean());
@@ -295,6 +310,69 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_USE_METERED_FIREWALL_CHAINS)
+    public void testMeteredNetworkRestrictionsByAdminChain() {
+        mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, TEST_UID,
+                FIREWALL_RULE_DENY);
+        verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, TEST_UID,
+                FIREWALL_RULE_DENY);
+        assertTrue("Should be true since mobile data usage is restricted by admin chain",
+                mNMService.isNetworkRestricted(TEST_UID));
+
+        mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, TEST_UID,
+                FIREWALL_RULE_DEFAULT);
+        verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, TEST_UID,
+                FIREWALL_RULE_DEFAULT);
+        assertFalse("Should be false since mobile data usage is no longer restricted by admin",
+                mNMService.isNetworkRestricted(TEST_UID));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_USE_METERED_FIREWALL_CHAINS)
+    public void testMeteredNetworkRestrictionsByUserChain() {
+        mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_USER, TEST_UID,
+                FIREWALL_RULE_DENY);
+        verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, TEST_UID,
+                FIREWALL_RULE_DENY);
+        assertTrue("Should be true since mobile data usage is restricted by user chain",
+                mNMService.isNetworkRestricted(TEST_UID));
+
+        mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_USER, TEST_UID,
+                FIREWALL_RULE_DEFAULT);
+        verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, TEST_UID,
+                FIREWALL_RULE_DEFAULT);
+        assertFalse("Should be false since mobile data usage is no longer restricted by user",
+                mNMService.isNetworkRestricted(TEST_UID));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_USE_METERED_FIREWALL_CHAINS)
+    public void testDataSaverRestrictionsWithAllowChain() {
+        mNMService.setDataSaverModeEnabled(true);
+        verify(mCm).setDataSaverEnabled(true);
+
+        assertTrue("Should be true since data saver is on and the uid is not allowlisted",
+                mNMService.isNetworkRestricted(TEST_UID));
+
+        mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_ALLOW, TEST_UID, FIREWALL_RULE_ALLOW);
+        verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_ALLOW, TEST_UID, FIREWALL_RULE_ALLOW);
+        assertFalse("Should be false since data saver is on and the uid is allowlisted",
+                mNMService.isNetworkRestricted(TEST_UID));
+
+        // remove uid from allowlist and turn datasaver off again
+
+        mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_ALLOW, TEST_UID,
+                FIREWALL_RULE_DEFAULT);
+        verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_ALLOW, TEST_UID,
+                FIREWALL_RULE_DEFAULT);
+        mNMService.setDataSaverModeEnabled(false);
+        verify(mCm).setDataSaverEnabled(false);
+
+        assertFalse("Network should not be restricted when data saver is off",
+                mNMService.isNetworkRestricted(TEST_UID));
+    }
+
+    @Test
     public void testFirewallChains() {
         final ArrayMap<Integer, ArrayMap<Integer, Boolean>> expected = new ArrayMap<>();
         // Dozable chain
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 9fc46c5..2f3bca0 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -1786,10 +1786,10 @@
                     /* matchesInterruptionFilter= */ false,
                     /* visibilityOverride= */ 0,
                     /* suppressedVisualEffects= */ 0,
-                    mParentNotificationChannel.getImportance(),
+                    mNotificationChannel.getImportance(),
                     /* explanation= */ null,
                     /* overrideGroupKey= */ null,
-                    mParentNotificationChannel,
+                    mNotificationChannel,
                     /* overridePeople= */ null,
                     /* snoozeCriteria= */ null,
                     /* showBadge= */ true,
diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
index 5902caa..d0acacc 100644
--- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -26,12 +26,15 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -39,9 +42,12 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.content.Context;
+import android.hardware.power.ChannelConfig;
+import android.hardware.power.IPower;
 import android.hardware.power.SessionConfig;
 import android.hardware.power.SessionTag;
 import android.hardware.power.WorkDuration;
@@ -50,6 +56,7 @@
 import android.os.IHintSession;
 import android.os.PerformanceHintManager;
 import android.os.Process;
+import android.os.RemoteException;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -130,12 +137,15 @@
     @Mock
     private HintManagerService.NativeWrapper mNativeWrapperMock;
     @Mock
+    private IPower mIPowerMock;
+    @Mock
     private ActivityManagerInternal mAmInternalMock;
     @Rule
     public final CheckFlagsRule mCheckFlagsRule =
             DeviceFlagsValueProvider.createCheckFlagsRule();
 
     private HintManagerService mService;
+    private ChannelConfig mConfig;
 
     private static Answer<Long> fakeCreateWithConfig(Long ptr, Long sessionId) {
         return new Answer<Long>() {
@@ -149,6 +159,9 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        mConfig = new ChannelConfig();
+        mConfig.readFlagBitmask = 1;
+        mConfig.writeFlagBitmask = 2;
         when(mNativeWrapperMock.halGetHintSessionPreferredRate())
                 .thenReturn(DEFAULT_HINT_PREFERRED_RATE);
         when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A),
@@ -170,6 +183,8 @@
                 any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[2],
                     SESSION_IDS[2]));
 
+        when(mIPowerMock.getInterfaceVersion()).thenReturn(5);
+        when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenReturn(mConfig);
         LocalServices.removeServiceForTest(ActivityManagerInternal.class);
         LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock);
     }
@@ -252,6 +267,9 @@
             NativeWrapper createNativeWrapper() {
                 return mNativeWrapperMock;
             }
+            IPower createIPower() {
+                return mIPowerMock;
+            }
         });
         return mService;
     }
@@ -261,6 +279,9 @@
             NativeWrapper createNativeWrapper() {
                 return new NativeWrapperFake();
             }
+            IPower createIPower() {
+                return mIPowerMock;
+            }
         });
         return mService;
     }
@@ -728,6 +749,102 @@
         verify(mNativeWrapperMock, never()).halSetMode(anyLong(), anyInt(), anyBoolean());
     }
 
+    @Test
+    public void testGetChannel() throws Exception {
+        HintManagerService service = createService();
+        Binder token = new Binder();
+
+        // Should only call once, after caching the first call
+        ChannelConfig config = service.getBinderServiceInstance().getSessionChannel(token);
+        ChannelConfig config2 = service.getBinderServiceInstance().getSessionChannel(token);
+        verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID));
+        assertEquals(config.readFlagBitmask, mConfig.readFlagBitmask);
+        assertEquals(config.writeFlagBitmask, mConfig.writeFlagBitmask);
+        assertEquals(config2.readFlagBitmask, mConfig.readFlagBitmask);
+        assertEquals(config2.writeFlagBitmask, mConfig.writeFlagBitmask);
+    }
+
+    @Test
+    public void testGetChannelTwice() throws Exception {
+        HintManagerService service = createService();
+        Binder token = new Binder();
+
+        service.getBinderServiceInstance().getSessionChannel(token);
+        verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID));
+        service.getBinderServiceInstance().closeSessionChannel();
+        verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID));
+
+        clearInvocations(mIPowerMock);
+
+        service.getBinderServiceInstance().getSessionChannel(token);
+        verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID));
+        service.getBinderServiceInstance().closeSessionChannel();
+        verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID));
+    }
+
+    @Test
+    public void testGetChannelFails() throws Exception {
+        HintManagerService service = createService();
+        Binder token = new Binder();
+
+        when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenThrow(RemoteException.class);
+
+        assertThrows(IllegalStateException.class, () -> {
+            service.getBinderServiceInstance().getSessionChannel(token);
+        });
+    }
+
+
+    @Test
+    public void testGetChannelBadVersion() throws Exception {
+        when(mIPowerMock.getInterfaceVersion()).thenReturn(3);
+        HintManagerService service = createService();
+        Binder token = new Binder();
+
+        reset(mIPowerMock);
+        when(mIPowerMock.getInterfaceVersion()).thenReturn(3);
+        when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenReturn(mConfig);
+
+        ChannelConfig channel = service.getBinderServiceInstance().getSessionChannel(token);
+        verify(mIPowerMock, times(0)).getSessionChannel(eq(TGID), eq(UID));
+        assertNull(channel);
+    }
+
+    @Test
+    public void testCloseChannel() throws Exception {
+        HintManagerService service = createService();
+        Binder token = new Binder();
+
+        service.getBinderServiceInstance().getSessionChannel(token);
+        service.getBinderServiceInstance().closeSessionChannel();
+        verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID));
+    }
+
+    @Test
+    public void testCloseChannelFails() throws Exception {
+        HintManagerService service = createService();
+        Binder token = new Binder();
+
+        service.getBinderServiceInstance().getSessionChannel(token);
+
+        doThrow(RemoteException.class).when(mIPowerMock).closeSessionChannel(anyInt(), anyInt());
+
+        assertThrows(IllegalStateException.class, () -> {
+            service.getBinderServiceInstance().closeSessionChannel();
+        });
+    }
+
+    @Test
+    public void testDoubleClose() throws Exception {
+        HintManagerService service = createService();
+        Binder token = new Binder();
+
+        service.getBinderServiceInstance().getSessionChannel(token);
+        service.getBinderServiceInstance().closeSessionChannel();
+        service.getBinderServiceInstance().closeSessionChannel();
+        verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID));
+    }
+
     // This test checks that concurrent operations from different threads on IHintService,
     // IHintSession and UidObserver will not cause data race or deadlock. Ideally we should also
     // check the output of threads' reportActualDuration performance to detect lock starvation
@@ -935,4 +1052,40 @@
         a.reportActualWorkDuration2(WORK_DURATIONS_FIVE);
         verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any());
     }
+
+    @Test
+    public void testChannelDiesWhenTokenDies() throws Exception {
+        HintManagerService service = createService();
+
+        class DyingToken extends Binder {
+            DeathRecipient mToNotify;
+            @Override
+            public void linkToDeath(@NonNull DeathRecipient recipient, int flags) {
+                mToNotify = recipient;
+                super.linkToDeath(recipient, flags);
+            }
+
+            public void fakeDeath() {
+                mToNotify.binderDied();
+            }
+        }
+
+        DyingToken token = new DyingToken();
+
+        service.getBinderServiceInstance().getSessionChannel(token);
+        verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID));
+        assertTrue(service.hasChannel(TGID, UID));
+
+        token.fakeDeath();
+
+        assertFalse(service.hasChannel(TGID, UID));
+        verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID));
+
+        clearInvocations(mIPowerMock);
+
+        token = new DyingToken();
+        service.getBinderServiceInstance().getSessionChannel(token);
+        verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID));
+        assertTrue(service.hasChannel(TGID, UID));
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 4af20a9..70a0038 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -24,6 +24,8 @@
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_MIN;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
 
@@ -52,6 +54,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.Manifest.permission;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.KeyguardManager;
@@ -67,6 +70,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.UserInfo;
+import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.drawable.Icon;
 import android.media.AudioAttributes;
@@ -93,6 +97,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.R;
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
 import com.android.internal.config.sysui.TestableFlagResolver;
 import com.android.internal.logging.InstanceIdSequence;
@@ -188,6 +193,8 @@
         getContext().addMockSystemService(Vibrator.class, mVibrator);
         getContext().addMockSystemService(PackageManager.class, mPackageManager);
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(false);
+        when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST),
+                anyString())).thenReturn(PERMISSION_DENIED);
 
         when(mAudioManager.isAudioFocusExclusive()).thenReturn(false);
         when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer);
@@ -210,6 +217,16 @@
         verify(mAccessibilityService).addClient(any(IAccessibilityManagerClient.class), anyInt());
         assertTrue(mAccessibilityManager.isEnabled());
 
+        // Enable LED pulse setting by default
+        Settings.System.putInt(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_LIGHT_PULSE, 1);
+
+        Resources resources = spy(getContext().getResources());
+        when(resources.getBoolean(R.bool.config_useAttentionLight)).thenReturn(true);
+        when(resources.getBoolean(
+                com.android.internal.R.bool.config_intrusiveNotificationLed)).thenReturn(true);
+        when(getContext().getResources()).thenReturn(resources);
+
         // TODO (b/291907312): remove feature flag
         // Disable feature flags by default. Tests should enable as needed.
         mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS,
@@ -239,7 +256,6 @@
         mAttentionHelper.setKeyguardManager(mKeyguardManager);
         mAttentionHelper.setScreenOn(false);
         mAttentionHelper.setInCallStateOffHook(false);
-        mAttentionHelper.mNotificationPulseEnabled = true;
 
         if (Flags.crossAppPoliteNotifications()) {
             // Capture BroadcastReceiver for avalanche triggers
@@ -611,6 +627,14 @@
         verify(mLight, times(1)).setFlashing(anyInt(), anyInt(), anyInt(), anyInt());
     }
 
+    private void verifyAttentionLights() {
+        verify(mLight, times(1)).pulse();
+    }
+
+    private void verifyNeverAttentionLights() {
+        verify(mLight, never()).pulse();
+    }
+
     //
     // Tests
     //
@@ -1524,7 +1548,10 @@
 
     @Test
     public void testLightsLightsOffGlobally() {
-        mAttentionHelper.mNotificationPulseEnabled = false;
+        Settings.System.putInt(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_LIGHT_PULSE, 0);
+        initAttentionHelper(mTestFlagResolver);
+
         NotificationRecord r = getLightsNotification();
         mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
         verifyNeverLights();
@@ -1533,6 +1560,44 @@
     }
 
     @Test
+    public void testLightsLightsResConfigDisabled() {
+        Resources resources = spy(getContext().getResources());
+        when(resources.getBoolean(
+                com.android.internal.R.bool.config_intrusiveNotificationLed)).thenReturn(false);
+        when(getContext().getResources()).thenReturn(resources);
+        initAttentionHelper(mTestFlagResolver);
+
+        NotificationRecord r = getLightsNotification();
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyNeverLights();
+        assertFalse(r.isInterruptive());
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
+    }
+
+    @Test
+    public void testLightsUseAttentionLight() {
+        NotificationRecord r = getLightsNotification();
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyAttentionLights();
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
+    }
+
+    @Test
+    public void testLightsUseAttentionLightDisabled() {
+        Resources resources = spy(getContext().getResources());
+        when(resources.getBoolean(R.bool.config_useAttentionLight)).thenReturn(false);
+        when(resources.getBoolean(
+                com.android.internal.R.bool.config_intrusiveNotificationLed)).thenReturn(true);
+        when(getContext().getResources()).thenReturn(resources);
+        initAttentionHelper(mTestFlagResolver);
+
+        NotificationRecord r = getLightsNotification();
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyNeverAttentionLights();
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
+    }
+
+    @Test
     public void testLightsDndIntercepted() {
         NotificationRecord r = getLightsNotification();
         r.setSuppressedVisualEffects(SUPPRESSED_EFFECT_LIGHTS);
@@ -2303,6 +2368,72 @@
     }
 
     @Test
+    public void testBeepVolume_politeNotif_Avalanche_exemptEmergency() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        initAttentionHelper(flagResolver);
+
+        // Trigger avalanche trigger intent
+        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        intent.putExtra("state", false);
+        mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+
+        NotificationRecord r = getBeepyNotification();
+
+        // Grant RECEIVE_EMERGENCY_BROADCAST to notification's package
+        when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST),
+                eq(r.getSbn().getPackageName()))).thenReturn(PERMISSION_GRANTED);
+
+        // Should beep at 100% volume
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyBeepVolume(1.0f);
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+        verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
+    }
+
+    @Test
+    public void testBeepVolume_politeNotif_exemptEmergency() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        // NOTIFICATION_COOLDOWN_ALL setting is enabled
+        Settings.System.putInt(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+        initAttentionHelper(flagResolver);
+
+        NotificationRecord r = getBeepyNotification();
+
+        // Grant RECEIVE_EMERGENCY_BROADCAST to notification's package
+        when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST),
+                eq(r.getSbn().getPackageName()))).thenReturn(PERMISSION_GRANTED);
+
+        // set up internal state
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        Mockito.reset(mRingtonePlayer);
+
+        // update should beep at 100% volume
+        NotificationRecord r2 = getBeepyNotification();
+        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+        assertNotEquals(-1, r2.getLastAudiblyAlertedMs());
+        verifyBeepVolume(1.0f);
+
+        // 2nd update should beep at 100% volume
+        Mockito.reset(mRingtonePlayer);
+        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+        assertNotEquals(-1, r2.getLastAudiblyAlertedMs());
+        verifyBeepVolume(1.0f);
+
+        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+    }
+
+    @Test
     public void testBeepVolume_politeNotif_applyPerApp() throws Exception {
         mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
         mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index b366f92..e564ba6 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -99,6 +99,7 @@
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
+import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_LOCKDOWN;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
@@ -112,6 +113,7 @@
 import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
 import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
 import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL;
+import static com.android.server.notification.Flags.FLAG_REJECT_OLD_NOTIFICATIONS;
 import static com.android.server.notification.NotificationManagerService.BITMAP_DURATION;
 import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
 import static com.android.server.notification.NotificationManagerService.NOTIFICATION_TTL;
@@ -339,9 +341,9 @@
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
@@ -503,7 +505,7 @@
     @Mock
     MultiRateLimiter mToastRateLimiter;
     BroadcastReceiver mPackageIntentReceiver;
-    BroadcastReceiver mUserSwitchIntentReceiver;
+    BroadcastReceiver mUserIntentReceiver;
     BroadcastReceiver mNotificationTimeoutReceiver;
     NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
     TestableNotificationManagerService.StrongAuthTrackerFake mStrongAuthTracker;
@@ -802,11 +804,13 @@
                     && filter.hasAction(Intent.ACTION_PACKAGES_SUSPENDED)) {
                 mPackageIntentReceiver = broadcastReceivers.get(i);
             }
-            if (filter.hasAction(Intent.ACTION_USER_SWITCHED)) {
+            if (filter.hasAction(Intent.ACTION_USER_SWITCHED)
+                    || filter.hasAction(Intent.ACTION_PROFILE_UNAVAILABLE)
+                    || filter.hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) {
                 // There may be multiple receivers, get the NMS one
                 if (broadcastReceivers.get(i).toString().contains(
                         NotificationManagerService.class.getName())) {
-                    mUserSwitchIntentReceiver = broadcastReceivers.get(i);
+                    mUserIntentReceiver = broadcastReceivers.get(i);
                 }
             }
             if (filter.hasAction(ACTION_NOTIFICATION_TIMEOUT)
@@ -815,7 +819,7 @@
             }
         }
         assertNotNull("package intent receiver should exist", mPackageIntentReceiver);
-        assertNotNull("User-switch receiver should exist", mUserSwitchIntentReceiver);
+        assertNotNull("User receiver should exist", mUserIntentReceiver);
         if (!Flags.allNotifsNeedTtl()) {
             assertNotNull("Notification timeout receiver should exist",
                     mNotificationTimeoutReceiver);
@@ -908,7 +912,9 @@
         }
 
         mService.clearNotifications();
-        TestableLooper.get(this).processAllMessages();
+        if (mTestableLooper != null) {
+            mTestableLooper.processAllMessages();
+        }
 
         try {
             mService.onDestroy();
@@ -919,14 +925,16 @@
 
         InstrumentationRegistry.getInstrumentation()
                 .getUiAutomation().dropShellPermissionIdentity();
-        // Remove scheduled messages that would be processed when the test is already done, and
-        // could cause issues, for example, messages that remove/cancel shown toasts (this causes
-        // problematic interactions with mocks when they're no longer working as expected).
-        mWorkerHandler.removeCallbacksAndMessages(null);
+        if (mWorkerHandler != null) {
+            // Remove scheduled messages that would be processed when the test is already done, and
+            // could cause issues, for example, messages that remove/cancel shown toasts (this causes
+            // problematic interactions with mocks when they're no longer working as expected).
+            mWorkerHandler.removeCallbacksAndMessages(null);
+        }
 
-        if (TestableLooper.get(this) != null) {
+        if (mTestableLooper != null) {
             // Must remove static reference to this test object to prevent leak (b/261039202)
-            TestableLooper.remove(this);
+            mTestableLooper.remove(this);
         }
     }
 
@@ -976,7 +984,7 @@
     private void simulateProfileAvailabilityActions(String intentAction) {
         final Intent intent = new Intent(intentAction);
         intent.putExtra(Intent.EXTRA_USER_HANDLE, TEST_PROFILE_USERHANDLE);
-        mUserSwitchIntentReceiver.onReceive(mContext, intent);
+        mUserIntentReceiver.onReceive(mContext, intent);
     }
 
     private ArrayMap<Boolean, ArrayList<ComponentName>> generateResetComponentValues() {
@@ -1008,7 +1016,9 @@
     }
 
     public void waitForIdle() {
-        mTestableLooper.processAllMessages();
+        if (mTestableLooper != null) {
+            mTestableLooper.processAllMessages();
+        }
     }
 
     private void setUpPrefsForBubbles(String pkg, int uid, boolean globalEnabled,
@@ -1301,6 +1311,106 @@
         return nrSummary;
     }
 
+    private NotificationRecord createAndPostCallStyleNotification(String packageName,
+            UserHandle userHandle, String testName) throws Exception {
+        Person person = new Person.Builder().setName("caller").build();
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .setFlag(FLAG_USER_INITIATED_JOB, true)
+                .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+        StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, 1,
+                testName, mUid, 0, nb.build(), userHandle, null, 0);
+        NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mService.addEnqueuedNotification(r);
+        mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+                r.getUid(), mPostNotificationTrackerFactory.newTracker(null)).run();
+        waitForIdle();
+
+        return mService.findNotificationLocked(
+                packageName, r.getSbn().getTag(), r.getSbn().getId(), r.getSbn().getUserId());
+    }
+
+    private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName)
+            throws RemoteException {
+        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1, testName, mUid, 0,
+                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+        waitForIdle();
+
+        return mService.findNotificationLocked(
+                mPkg, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
+    }
+
+    private static <T extends Parcelable> T parcelAndUnparcel(T source,
+            Parcelable.Creator<T> creator) {
+        Parcel parcel = Parcel.obtain();
+        source.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        return creator.createFromParcel(parcel);
+    }
+
+    private PendingIntent createPendingIntent(String action) {
+        return PendingIntent.getActivity(mContext, 0,
+                new Intent(action).setPackage(mContext.getPackageName()),
+                PendingIntent.FLAG_MUTABLE);
+    }
+
+    private void allowTestPackageToToast() throws Exception {
+        assertWithMessage("toast queue").that(mService.mToastQueue).isEmpty();
+        mService.isSystemUid = false;
+        mService.isSystemAppId = false;
+        setToastRateIsWithinQuota(true);
+        setIfPackageHasPermissionToAvoidToastRateLimiting(TEST_PACKAGE, false);
+        // package is not suspended
+        when(mPackageManager.isPackageSuspendedForUser(TEST_PACKAGE, mUserId))
+                .thenReturn(false);
+    }
+
+    private boolean enqueueToast(String testPackage, ITransientNotification callback)
+            throws RemoteException {
+        return enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(),
+                callback);
+    }
+
+    private boolean enqueueToast(INotificationManager service, String testPackage,
+            IBinder token, ITransientNotification callback) throws RemoteException {
+        return service.enqueueToast(testPackage, token, callback, TOAST_DURATION, /* isUiContext= */
+                true, DEFAULT_DISPLAY);
+    }
+
+    private boolean enqueueTextToast(String testPackage, CharSequence text) throws RemoteException {
+        return enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY);
+    }
+
+    private boolean enqueueTextToast(String testPackage, CharSequence text, boolean isUiContext,
+            int displayId) throws RemoteException {
+        return ((INotificationManager) mService.mService).enqueueTextToast(testPackage,
+                new Binder(), text, TOAST_DURATION, isUiContext, displayId,
+                /* textCallback= */ null);
+    }
+
+    private void mockIsVisibleBackgroundUsersSupported(boolean supported) {
+        when(mUm.isVisibleBackgroundUsersSupported()).thenReturn(supported);
+    }
+
+    private void mockIsUserVisible(int displayId, boolean visible) {
+        when(mUmInternal.isUserVisible(mUserId, displayId)).thenReturn(visible);
+    }
+
+    private void mockDisplayAssignedToUser(int displayId) {
+        when(mUmInternal.getMainDisplayAssignedToUser(mUserId)).thenReturn(displayId);
+    }
+
+    private void verifyToastShownForTestPackage(String text, int displayId) {
+        verify(mStatusBar).showToast(eq(mUid), eq(TEST_PACKAGE), any(), eq(text), any(),
+                eq(TOAST_DURATION), any(), eq(displayId));
+    }
+
     @Test
     @DisableFlags(FLAG_ALL_NOTIFS_NEED_TTL)
     public void testLimitTimeOutBroadcast() {
@@ -5968,6 +6078,8 @@
         assertThat(captor.getValue().getNotification().flags
                 & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
                 FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
+        assertThat(captor.getValue().getNotification().flags
+                & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE);
         assertThat(captor.getValue().shouldPostSilently()).isTrue();
     }
 
@@ -8797,6 +8909,8 @@
         assertThat(captor.getValue().getNotification().flags
                 & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
                 FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
+        assertThat(captor.getValue().getNotification().flags
+                & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE);
         assertThat(captor.getValue().shouldPostSilently()).isTrue();
     }
 
@@ -14064,11 +14178,12 @@
     public void enqueueUpdate_whenBelowMaxEnqueueRate_accepts() throws Exception {
         // Post the first version.
         Notification original = generateNotificationRecord(null).getNotification();
-        original.when = 111;
+        original.when = System.currentTimeMillis();
         mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, original, mUserId);
         waitForIdle();
         assertThat(mService.mNotificationList).hasSize(1);
-        assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111);
+        assertThat(mService.mNotificationList.get(0).getNotification().when)
+                .isEqualTo(original.when);
 
         reset(mUsageStats);
         when(mUsageStats.getAppEnqueueRate(eq(mPkg)))
@@ -14076,7 +14191,7 @@
 
         // Post the update.
         Notification update = generateNotificationRecord(null).getNotification();
-        update.when = 222;
+        update.when = System.currentTimeMillis() + 111;
         mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, update, mUserId);
         waitForIdle();
 
@@ -14085,18 +14200,19 @@
         verify(mUsageStats, never()).registerPostedByApp(any());
         verify(mUsageStats).registerUpdatedByApp(any(), any());
         assertThat(mService.mNotificationList).hasSize(1);
-        assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(222);
+        assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(update.when);
     }
 
     @Test
     public void enqueueUpdate_whenAboveMaxEnqueueRate_rejects() throws Exception {
         // Post the first version.
         Notification original = generateNotificationRecord(null).getNotification();
-        original.when = 111;
+        original.when = System.currentTimeMillis();
         mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, original, mUserId);
         waitForIdle();
         assertThat(mService.mNotificationList).hasSize(1);
-        assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111);
+        assertThat(mService.mNotificationList.get(0).getNotification().when)
+                .isEqualTo(original.when);
 
         reset(mUsageStats);
         when(mUsageStats.getAppEnqueueRate(eq(mPkg)))
@@ -14104,7 +14220,7 @@
 
         // Post the update.
         Notification update = generateNotificationRecord(null).getNotification();
-        update.when = 222;
+        update.when = System.currentTimeMillis() + 111;
         mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, update, mUserId);
         waitForIdle();
 
@@ -14113,7 +14229,8 @@
         verify(mUsageStats, never()).registerPostedByApp(any());
         verify(mUsageStats, never()).registerUpdatedByApp(any(), any());
         assertThat(mService.mNotificationList).hasSize(1);
-        assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); // old
+        assertThat(mService.mNotificationList.get(0).getNotification().when)
+                .isEqualTo(original.when); // old
     }
 
     @Test
@@ -14482,13 +14599,33 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_USE_SSM_USER_SWITCH_SIGNAL)
     public void onUserSwitched_updatesZenModeAndChannelsBypassingDnd() {
+        mService.mZenModeHelper = mock(ZenModeHelper.class);
+        mService.setPreferencesHelper(mPreferencesHelper);
+
+        UserInfo prevUser = new UserInfo();
+        prevUser.id = 10;
+        UserInfo newUser = new UserInfo();
+        newUser.id = 20;
+
+        mService.onUserSwitching(new TargetUser(prevUser), new TargetUser(newUser));
+
+        InOrder inOrder = inOrder(mPreferencesHelper, mService.mZenModeHelper);
+        inOrder.verify(mService.mZenModeHelper).onUserSwitched(eq(20));
+        inOrder.verify(mPreferencesHelper).syncChannelsBypassingDnd();
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_USE_SSM_USER_SWITCH_SIGNAL)
+    public void onUserSwitched_broadcast_updatesZenModeAndChannelsBypassingDnd() {
         Intent intent = new Intent(Intent.ACTION_USER_SWITCHED);
         intent.putExtra(Intent.EXTRA_USER_HANDLE, 20);
         mService.mZenModeHelper = mock(ZenModeHelper.class);
         mService.setPreferencesHelper(mPreferencesHelper);
 
-        mUserSwitchIntentReceiver.onReceive(mContext, intent);
+        mUserIntentReceiver.onReceive(mContext, intent);
 
         InOrder inOrder = inOrder(mPreferencesHelper, mService.mZenModeHelper);
         inOrder.verify(mService.mZenModeHelper).onUserSwitched(eq(20));
@@ -15458,103 +15595,126 @@
         assertThat(n.getTimeoutAfter()).isEqualTo(20);
     }
 
-    private NotificationRecord createAndPostCallStyleNotification(String packageName,
-            UserHandle userHandle, String testName) throws Exception {
-        Person person = new Person.Builder().setName("caller").build();
-        Notification.Builder nb = new Notification.Builder(mContext,
-                mTestNotificationChannel.getId())
-                .setFlag(FLAG_USER_INITIATED_JOB, true)
-                .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
-                .setSmallIcon(android.R.drawable.sym_def_app_icon);
-        StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, 1,
-                testName, mUid, 0, nb.build(), userHandle, null, 0);
+    @Test
+    @EnableFlags(FLAG_REJECT_OLD_NOTIFICATIONS)
+    public void testRejectOldNotification_oldWhen() throws Exception {
+        Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setWhen(System.currentTimeMillis() - Duration.ofDays(15).toMillis())
+                .build();
+        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0,
+                n, UserHandle.getUserHandleForUid(mUid), null, 0);
         NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mService.addEnqueuedNotification(r);
-        mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
-                r.getUid(), mPostNotificationTrackerFactory.newTracker(null)).run();
-        waitForIdle();
-
-        return mService.findNotificationLocked(
-                packageName, r.getSbn().getTag(), r.getSbn().getId(), r.getSbn().getUserId());
+        assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false))
+                .isFalse();
     }
 
-    private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName)
-            throws RemoteException {
-        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1, testName, mUid, 0,
-                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
-        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+    @Test
+    @EnableFlags(FLAG_REJECT_OLD_NOTIFICATIONS)
+    public void testRejectOldNotification_mediumOldWhen() throws Exception {
+        Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setWhen(System.currentTimeMillis() - Duration.ofDays(13).toMillis())
+                .build();
+        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0,
+                n, UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(),
-                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
-        waitForIdle();
-
-        return mService.findNotificationLocked(
-                mPkg, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
+        assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false))
+                .isTrue();
     }
 
-    private static <T extends Parcelable> T parcelAndUnparcel(T source,
-            Parcelable.Creator<T> creator) {
-        Parcel parcel = Parcel.obtain();
-        source.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        return creator.createFromParcel(parcel);
+    @Test
+    @EnableFlags(FLAG_REJECT_OLD_NOTIFICATIONS)
+    public void testRejectOldNotification_zeroWhen() throws Exception {
+        Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setWhen(0)
+                .build();
+        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0,
+                n, UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false))
+                .isTrue();
     }
 
-    private PendingIntent createPendingIntent(String action) {
-        return PendingIntent.getActivity(mContext, 0,
-                new Intent(action).setPackage(mContext.getPackageName()),
-                PendingIntent.FLAG_MUTABLE);
+    @Test
+    public void testClearUIJFromUninstallingPackage() throws Exception {
+        NotificationRecord r =
+                generateNotificationRecord(mTestNotificationChannel, 0, mUserId, "bar");
+        mService.addNotification(r);
+
+        when(mPackageManagerClient.getPackageUidAsUser(anyString(), anyInt()))
+                .thenThrow(PackageManager.NameNotFoundException.class);
+        when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(false);
+
+        mInternalService.cancelNotification(mPkg, mPkg, mUid, 0, r.getSbn().getTag(),
+                r.getSbn().getId(), mUserId);
+
+        // no exception
     }
 
-    private void allowTestPackageToToast() throws Exception {
-        assertWithMessage("toast queue").that(mService.mToastQueue).isEmpty();
+    @Test
+    public void testPostFromMissingPackage_throws() throws Exception {
+        NotificationRecord r =
+                generateNotificationRecord(mTestNotificationChannel, 0, mUserId, "bar");
+
+        when(mPackageManagerClient.getPackageUidAsUser(anyString(), anyInt()))
+                .thenThrow(PackageManager.NameNotFoundException.class);
+        when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(false);
+
+        try {
+            mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
+                    r.getSbn().getId(), r.getSbn().getNotification(),
+                    r.getSbn().getUserId());
+            fail("Allowed to post a notification for an absent package");
+        } catch (SecurityException e) {
+            // yay
+        }
+    }
+
+    @Test
+    public void testGetEffectsSuppressor_noSuppressor() throws Exception {
+        when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId});
+        when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+        when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(true);
+        assertThat(mBinderService.getEffectsSuppressor()).isNull();
+    }
+
+    @Test
+    public void testGetEffectsSuppressor_suppressorSameApp() throws Exception {
+        when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId});
+        when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
         mService.isSystemUid = false;
         mService.isSystemAppId = false;
-        setToastRateIsWithinQuota(true);
-        setIfPackageHasPermissionToAvoidToastRateLimiting(TEST_PACKAGE, false);
-        // package is not suspended
-        when(mPackageManager.isPackageSuspendedForUser(TEST_PACKAGE, mUserId))
-                .thenReturn(false);
+        mBinderService.requestHintsFromListener(mock(INotificationListener.class),
+                HINT_HOST_DISABLE_EFFECTS);
+        when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(true);
+        assertThat(mBinderService.getEffectsSuppressor()).isEqualTo(mListener.component);
     }
 
-    private boolean enqueueToast(String testPackage, ITransientNotification callback)
-            throws RemoteException {
-        return enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(),
-                callback);
+    @Test
+    public void testGetEffectsSuppressor_suppressorDiffApp() throws Exception {
+        when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId});
+        when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+        mService.isSystemUid = false;
+        mService.isSystemAppId = false;
+        mBinderService.requestHintsFromListener(mock(INotificationListener.class),
+                HINT_HOST_DISABLE_EFFECTS);
+        when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(false);
+        assertThat(mBinderService.getEffectsSuppressor()).isEqualTo(null);
     }
 
-    private boolean enqueueToast(INotificationManager service, String testPackage,
-            IBinder token, ITransientNotification callback) throws RemoteException {
-        return service.enqueueToast(testPackage, token, callback, TOAST_DURATION, /* isUiContext= */
-                true, DEFAULT_DISPLAY);
-    }
-
-    private boolean enqueueTextToast(String testPackage, CharSequence text) throws RemoteException {
-        return enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY);
-    }
-
-    private boolean enqueueTextToast(String testPackage, CharSequence text, boolean isUiContext,
-            int displayId) throws RemoteException {
-        return ((INotificationManager) mService.mService).enqueueTextToast(testPackage,
-                new Binder(), text, TOAST_DURATION, isUiContext, displayId,
-                /* textCallback= */ null);
-    }
-
-    private void mockIsVisibleBackgroundUsersSupported(boolean supported) {
-        when(mUm.isVisibleBackgroundUsersSupported()).thenReturn(supported);
-    }
-
-    private void mockIsUserVisible(int displayId, boolean visible) {
-        when(mUmInternal.isUserVisible(mUserId, displayId)).thenReturn(visible);
-    }
-
-    private void mockDisplayAssignedToUser(int displayId) {
-        when(mUmInternal.getMainDisplayAssignedToUser(mUserId)).thenReturn(displayId);
-    }
-
-    private void verifyToastShownForTestPackage(String text, int displayId) {
-        verify(mStatusBar).showToast(eq(mUid), eq(TEST_PACKAGE), any(), eq(text), any(),
-                eq(TOAST_DURATION), any(), eq(displayId));
+    @Test
+    public void testGetEffectsSuppressor_suppressorDiffAppSystemCaller() throws Exception {
+        when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId});
+        when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+        mService.isSystemUid = true;
+        mBinderService.requestHintsFromListener(mock(INotificationListener.class),
+                HINT_HOST_DISABLE_EFFECTS);
+        when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(false);
+        assertThat(mBinderService.getEffectsSuppressor()).isEqualTo(mListener.component);
     }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 5fdb396..d1423fe 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -3002,39 +3002,45 @@
         assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode);
     }
 
-    private enum ModesApiFlag {
-        ENABLED(true, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_USER),
-        DISABLED(false, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI);
+    private enum ModesFlag {
+        MODES_UI(2, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_USER),
+        MODES_API(1, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_USER),
+        DISABLED(0, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI);
 
-        private final boolean mEnabled;
+        private final int mFlagsEnabled;
         @ConfigChangeOrigin
         private final int mOriginForUserActionInSystemUi;
 
-        ModesApiFlag(boolean enabled, @ConfigChangeOrigin int originForUserActionInSystemUi) {
-            this.mEnabled = enabled;
+        ModesFlag(int flagsEnabled, @ConfigChangeOrigin int originForUserActionInSystemUi) {
+            this.mFlagsEnabled = flagsEnabled;
             this.mOriginForUserActionInSystemUi = originForUserActionInSystemUi;
         }
 
-        void applyFlag(SetFlagsRule setFlagsRule) {
-            if (mEnabled) {
+        void applyFlags(SetFlagsRule setFlagsRule) {
+            if (mFlagsEnabled >= 1) {
                 setFlagsRule.enableFlags(Flags.FLAG_MODES_API);
             } else {
                 setFlagsRule.disableFlags(Flags.FLAG_MODES_API);
             }
+            if (mFlagsEnabled >= 2) {
+                setFlagsRule.enableFlags(Flags.FLAG_MODES_UI);
+            } else {
+                setFlagsRule.disableFlags(Flags.FLAG_MODES_UI);
+            }
         }
     }
 
     @Test
-    public void testZenModeEventLog_setManualZenMode(@TestParameter ModesApiFlag modesApiFlag)
+    public void testZenModeEventLog_setManualZenMode(@TestParameter ModesFlag modesFlag)
             throws IllegalArgumentException {
-        modesApiFlag.applyFlag(mSetFlagsRule);
+        modesFlag.applyFlags(mSetFlagsRule);
         mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
         setupZenConfig();
 
         // Turn zen mode on (to important_interruptions)
         // Need to additionally call the looper in order to finish the post-apply-config process
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
-                modesApiFlag.mOriginForUserActionInSystemUi, "", null, Process.SYSTEM_UID);
+                modesFlag.mOriginForUserActionInSystemUi, "", null, Process.SYSTEM_UID);
 
         // Now turn zen mode off, but via a different package UID -- this should get registered as
         // "not an action by the user" because some other app is changing zen mode
@@ -3062,7 +3068,7 @@
         assertEquals(DNDProtoEnums.MANUAL_RULE, mZenModeEventLogger.getChangedRuleType(0));
         assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
         assertThat(mZenModeEventLogger.getFromSystemOrSystemUi(0)).isEqualTo(
-                modesApiFlag == ModesApiFlag.DISABLED);
+                modesFlag == ModesFlag.DISABLED);
         assertTrue(mZenModeEventLogger.getIsUserAction(0));
         assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(0));
         checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(0));
@@ -3091,9 +3097,9 @@
     }
 
     @Test
-    public void testZenModeEventLog_automaticRules(@TestParameter ModesApiFlag modesApiFlag)
+    public void testZenModeEventLog_automaticRules(@TestParameter ModesFlag modesFlag)
             throws IllegalArgumentException {
-        modesApiFlag.applyFlag(mSetFlagsRule);
+        modesFlag.applyFlags(mSetFlagsRule);
         mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
         setupZenConfig();
 
@@ -3116,7 +3122,7 @@
         // Event 2: "User" turns off the automatic rule (sets it to not enabled)
         zenRule.setEnabled(false);
         mZenModeHelper.updateAutomaticZenRule(id, zenRule,
-                modesApiFlag.mOriginForUserActionInSystemUi, "", Process.SYSTEM_UID);
+                modesFlag.mOriginForUserActionInSystemUi, "", Process.SYSTEM_UID);
 
         // Add a new system rule
         AutomaticZenRule systemRule = new AutomaticZenRule("systemRule",
@@ -3134,7 +3140,7 @@
                 UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // Event 4: "User" deletes the rule
-        mZenModeHelper.removeAutomaticZenRule(systemId, modesApiFlag.mOriginForUserActionInSystemUi,
+        mZenModeHelper.removeAutomaticZenRule(systemId, modesFlag.mOriginForUserActionInSystemUi,
                 "", Process.SYSTEM_UID);
 
         // In total, this represents 4 events
@@ -3282,22 +3288,22 @@
     }
 
     @Test
-    public void testZenModeEventLog_policyChanges(@TestParameter ModesApiFlag modesApiFlag)
+    public void testZenModeEventLog_policyChanges(@TestParameter ModesFlag modesFlag)
             throws IllegalArgumentException {
-        modesApiFlag.applyFlag(mSetFlagsRule);
+        modesFlag.applyFlags(mSetFlagsRule);
         mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
         setupZenConfig();
 
         // First just turn zen mode on
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
-                modesApiFlag.mOriginForUserActionInSystemUi, "", null, Process.SYSTEM_UID);
+                modesFlag.mOriginForUserActionInSystemUi, "", null, Process.SYSTEM_UID);
 
         // Now change the policy slightly; want to confirm that this'll be reflected in the logs
         ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
         newConfig.allowAlarms = true;
         newConfig.allowRepeatCallers = false;
         mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
-                modesApiFlag.mOriginForUserActionInSystemUi, Process.SYSTEM_UID);
+                modesFlag.mOriginForUserActionInSystemUi, Process.SYSTEM_UID);
 
         // Turn zen mode off; we want to make sure policy changes do not get logged when zen mode
         // is off.
@@ -3308,7 +3314,7 @@
         newConfig.allowMessages = false;
         newConfig.allowRepeatCallers = true;
         mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
-                modesApiFlag.mOriginForUserActionInSystemUi, Process.SYSTEM_UID);
+                modesFlag.mOriginForUserActionInSystemUi, Process.SYSTEM_UID);
 
         // Total events: we only expect ones for turning on, changing policy, and turning off
         assertEquals(3, mZenModeEventLogger.numLoggedChanges());
@@ -3341,9 +3347,9 @@
     }
 
     @Test
-    public void testZenModeEventLog_ruleCounts(@TestParameter ModesApiFlag modesApiFlag)
+    public void testZenModeEventLog_ruleCounts(@TestParameter ModesFlag modesFlag)
             throws IllegalArgumentException {
-        modesApiFlag.applyFlag(mSetFlagsRule);
+        modesFlag.applyFlags(mSetFlagsRule);
         mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
         setupZenConfig();
 
@@ -3447,9 +3453,9 @@
 
     @Test
     public void testZenModeEventLog_noLogWithNoConfigChange(
-            @TestParameter ModesApiFlag modesApiFlag) throws IllegalArgumentException {
+            @TestParameter ModesFlag modesFlag) throws IllegalArgumentException {
         // If evaluateZenMode is called independently of a config change, don't log.
-        modesApiFlag.applyFlag(mSetFlagsRule);
+        modesFlag.applyFlags(mSetFlagsRule);
         mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
         setupZenConfig();
 
@@ -3466,11 +3472,11 @@
     }
 
     @Test
-    public void testZenModeEventLog_reassignUid(@TestParameter ModesApiFlag modesApiFlag)
+    public void testZenModeEventLog_reassignUid(@TestParameter ModesFlag modesFlag)
             throws IllegalArgumentException {
         // Test that, only in specific cases, we reassign the calling UID to one associated with
         // the automatic rule owner.
-        modesApiFlag.applyFlag(mSetFlagsRule);
+        modesFlag.applyFlags(mSetFlagsRule);
         mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
         setupZenConfig();
 
@@ -3496,7 +3502,7 @@
                 manualRulePolicy,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
-                modesApiFlag.mOriginForUserActionInSystemUi, "test", Process.SYSTEM_UID);
+                modesFlag.mOriginForUserActionInSystemUi, "test", Process.SYSTEM_UID);
 
         // Turn on rule 1; call looks like it's from the system. Because setting a condition is
         // typically an automatic (non-user-initiated) action, expect the calling UID to be
@@ -3515,7 +3521,7 @@
         // from the system-provided one.
         zenRule.setEnabled(false);
         mZenModeHelper.updateAutomaticZenRule(id, zenRule,
-                modesApiFlag.mOriginForUserActionInSystemUi, "", Process.SYSTEM_UID);
+                modesFlag.mOriginForUserActionInSystemUi, "", Process.SYSTEM_UID);
 
         // Add a manual rule. Any manual rule changes should not get calling uids reassigned.
         mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
@@ -3573,9 +3579,9 @@
 
     @Test
     public void testZenModeEventLog_channelsBypassingChanges(
-            @TestParameter ModesApiFlag modesApiFlag) {
+            @TestParameter ModesFlag modesFlag) {
         // Verify that the right thing happens when the canBypassDnd value changes.
-        modesApiFlag.applyFlag(mSetFlagsRule);
+        modesFlag.applyFlags(mSetFlagsRule);
         mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
         setupZenConfig();
 
@@ -3847,8 +3853,9 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_MODES_API)
-    public void testUpdateConsolidatedPolicy_modesApiDefaultRulesOnly_takesDeviceDefault() {
+    public void testUpdateConsolidatedPolicy_modesApiDefaultRulesOnly_takesDefault(
+            @TestParameter({"MODES_UI", "MODES_API"}) ModesFlag modesFlag) {
+        modesFlag.applyFlags(mSetFlagsRule);
         setupZenConfig();
 
         // When there's one automatic rule active and it doesn't specify a policy, test that the
@@ -3869,7 +3876,9 @@
 
         // inspect the consolidated policy, which should match the device default settings.
         assertThat(ZenAdapters.notificationPolicyToZenPolicy(mZenModeHelper.mConsolidatedPolicy))
-                .isEqualTo(mZenModeHelper.getDefaultZenPolicy());
+                .isEqualTo(modesFlag == ModesFlag.MODES_UI
+                        ? mZenModeHelper.getDefaultZenPolicy()
+                        : mZenModeHelper.mConfig.toZenPolicy());
     }
 
     @Test
@@ -3904,7 +3913,8 @@
                 UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // since this is the only active rule, the consolidated policy should match the custom
-        // policy for every field specified, and take default values for unspecified things
+        // policy for every field specified, and take default values (from device default or
+        // manual policy) for unspecified things
         assertTrue(mZenModeHelper.mConsolidatedPolicy.allowAlarms());  // custom
         assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMedia());  // custom
         assertFalse(mZenModeHelper.mConsolidatedPolicy.allowSystem());  // default
@@ -3918,8 +3928,9 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_MODES_API)
-    public void testUpdateConsolidatedPolicy_modesApiCustomPolicyOnly_fillInWithDeviceDefault() {
+    public void testUpdateConsolidatedPolicy_modesApiCustomPolicyOnly_fillInWithDefault(
+            @TestParameter({"MODES_UI", "MODES_API"}) ModesFlag modesFlag) {
+        modesFlag.applyFlags(mSetFlagsRule);
         setupZenConfig();
 
         // when there's only one automatic rule active and it has a custom policy, make sure that's
@@ -3948,11 +3959,15 @@
                 UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // since this is the only active rule, the consolidated policy should match the custom
-        // policy for every field specified, and take default values for unspecified things
-        assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isTrue();  // default
-        assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isTrue();  // default
+        // policy for every field specified, and take default values (from either device default
+        // policy or manual rule) for unspecified things
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isEqualTo(
+                modesFlag == ModesFlag.MODES_UI ? true : false);  // default
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isEqualTo(
+                modesFlag == ModesFlag.MODES_UI ? true : false);  // default
         assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isTrue();  // custom
-        assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isFalse();  // default
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isEqualTo(
+                modesFlag == ModesFlag.MODES_UI ? false : true);  // default
         assertThat(mZenModeHelper.mConsolidatedPolicy.allowCalls()).isFalse();  // custom
         assertThat(mZenModeHelper.mConsolidatedPolicy.allowMessages()).isTrue(); // default
         assertThat(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers()).isFalse();  // custom
@@ -4022,8 +4037,9 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_MODES_API)
-    public void testUpdateConsolidatedPolicy_modesApiDefaultAndCustomActive_mergesWithDefault() {
+    public void testUpdateConsolidatedPolicy_modesApiDefaultAndCustomActive_mergesWithDefault(
+            @TestParameter({"MODES_UI", "MODES_API"}) ModesFlag modesFlag) {
+        modesFlag.applyFlags(mSetFlagsRule);
         setupZenConfig();
 
         // when there are two rules active, one inheriting the default policy and one setting its
@@ -4071,16 +4087,19 @@
         // now both rules should be on, and the consolidated policy should reflect the most
         // restrictive option of each of the two
         assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isFalse();  // custom stricter
-        assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isTrue();  // default
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isEqualTo(
+                modesFlag == ModesFlag.MODES_UI ? true : false);  // default
         assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isFalse();  // default stricter
-        assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isFalse();  // default
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isEqualTo(
+                modesFlag == ModesFlag.MODES_UI ? false : true);  // default
         assertThat(mZenModeHelper.mConsolidatedPolicy.allowCalls()).isFalse();  // custom stricter
         assertThat(mZenModeHelper.mConsolidatedPolicy.allowMessages()).isTrue(); // default
         assertThat(mZenModeHelper.mConsolidatedPolicy.allowConversations()).isTrue();  // default
         assertThat(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers())
                 .isFalse();  // custom stricter
         assertThat(mZenModeHelper.mConsolidatedPolicy.showBadges()).isFalse();  // custom stricter
-        assertThat(mZenModeHelper.mConsolidatedPolicy.showPeeking()).isFalse();  // default stricter
+        assertThat(mZenModeHelper.mConsolidatedPolicy.showPeeking()).isEqualTo(
+                modesFlag == ModesFlag.MODES_UI ? false : true);  // default
     }
 
     @Test
@@ -4134,8 +4153,9 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_MODES_API)
-    public void testUpdateConsolidatedPolicy_ignoresActiveRulesWithInterruptionFilterAll() {
+    public void testUpdateConsolidatedPolicy_ignoresActiveRulesWithInterruptionFilterAll(
+            @TestParameter({"MODES_UI", "MODES_API"}) ModesFlag modesFlag) {
+        modesFlag.applyFlags(mSetFlagsRule);
         setupZenConfig();
 
         // Rules with INTERRUPTION_FILTER_ALL are skipped when calculating consolidated policy.
@@ -4172,10 +4192,12 @@
                 UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
 
         // Consolidated Policy should be default + rule1.
-        assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isTrue();  // default
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isEqualTo(
+                modesFlag == ModesFlag.MODES_UI ? true : false);  // default
         assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isTrue(); // priority rule
         assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isTrue();  // priority rule
-        assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isFalse();  // default
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isEqualTo(
+                modesFlag == ModesFlag.MODES_UI ? false : true);  // default
         assertThat(mZenModeHelper.mConsolidatedPolicy.allowCalls()).isTrue();  // default
         assertThat(mZenModeHelper.mConsolidatedPolicy.allowMessages()).isTrue(); // default
         assertThat(mZenModeHelper.mConsolidatedPolicy.allowConversations()).isTrue();  // default
@@ -6251,7 +6273,7 @@
     }
 
     private void checkDndProtoMatchesDefaultZenConfig(DNDPolicyProto dndProto) {
-        if (!Flags.modesApi()) {
+        if (!Flags.modesUi()) {
             checkDndProtoMatchesSetupZenConfig(dndProto);
             return;
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
new file mode 100644
index 0000000..12ab3e1
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
@@ -0,0 +1,219 @@
+/*
+ * 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.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+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.servertransaction.RefreshCallbackItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link ActivityRefresher}.
+ *
+ * <p>Build/Install/Run:
+ *  atest WmTests:ActivityRefresherTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class ActivityRefresherTests extends WindowTestsBase {
+    private Handler mMockHandler;
+    private LetterboxConfiguration mLetterboxConfiguration;
+
+    private ActivityRecord mActivity;
+    private ActivityRefresher mActivityRefresher;
+
+    private ActivityRefresher.Evaluator mEvaluatorFalse =
+            (activity, newConfig, lastReportedConfig) -> false;
+
+    private ActivityRefresher.Evaluator mEvaluatorTrue =
+            (activity, newConfig, lastReportedConfig) -> true;
+
+    private final Configuration mNewConfig = new Configuration();
+    private final Configuration mOldConfig = new Configuration();
+
+    @Before
+    public void setUp() throws Exception {
+        mLetterboxConfiguration = mDisplayContent.mWmService.mLetterboxConfiguration;
+        spyOn(mLetterboxConfiguration);
+        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled())
+                .thenReturn(true);
+        when(mLetterboxConfiguration.isCameraCompatRefreshEnabled())
+                .thenReturn(true);
+        when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
+                .thenReturn(true);
+
+        mMockHandler = mock(Handler.class);
+        when(mMockHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
+                invocation -> {
+                    ((Runnable) invocation.getArgument(0)).run();
+                    return null;
+                });
+
+        mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mMockHandler);
+    }
+
+    @Test
+    public void testShouldRefreshActivity_refreshDisabled() throws Exception {
+        when(mLetterboxConfiguration.isCameraCompatRefreshEnabled())
+                .thenReturn(false);
+        configureActivityAndDisplay();
+        mActivityRefresher.addEvaluator(mEvaluatorTrue);
+
+        mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+        assertActivityRefreshRequested(/* refreshRequested= */ false);
+    }
+
+    @Test
+    public void testShouldRefreshActivity_refreshDisabledForActivity() throws Exception {
+        configureActivityAndDisplay();
+        when(mActivity.mLetterboxUiController.shouldRefreshActivityForCameraCompat())
+                .thenReturn(false);
+        mActivityRefresher.addEvaluator(mEvaluatorTrue);
+
+        mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+        assertActivityRefreshRequested(/* refreshRequested= */ false);
+    }
+
+    @Test
+    public void testShouldRefreshActivity_noRefreshTriggerers() throws Exception {
+        configureActivityAndDisplay();
+
+        mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+        assertActivityRefreshRequested(/* refreshRequested= */ false);
+    }
+
+    @Test
+    public void testShouldRefreshActivity_refreshTriggerersReturnFalse() throws Exception {
+        configureActivityAndDisplay();
+        mActivityRefresher.addEvaluator(mEvaluatorFalse);
+
+        mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+        assertActivityRefreshRequested(/* refreshRequested= */ false);
+    }
+
+    @Test
+    public void testShouldRefreshActivity_anyRefreshTriggerersReturnTrue() throws Exception {
+        configureActivityAndDisplay();
+        mActivityRefresher.addEvaluator(mEvaluatorFalse);
+        mActivityRefresher.addEvaluator(mEvaluatorTrue);
+
+        mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+        assertActivityRefreshRequested(/* refreshRequested= */ true);
+    }
+
+    @Test
+    public void testOnActivityConfigurationChanging_cycleThroughStopDisabled()
+            throws Exception {
+        mActivityRefresher.addEvaluator(mEvaluatorTrue);
+        when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
+                .thenReturn(false);
+        configureActivityAndDisplay();
+
+        mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+        assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
+    }
+
+    @Test
+    public void testOnActivityConfigurationChanging_cycleThroughPauseEnabledForApp()
+            throws Exception {
+        configureActivityAndDisplay();
+        mActivityRefresher.addEvaluator(mEvaluatorTrue);
+        doReturn(true).when(mActivity.mLetterboxUiController)
+                .shouldRefreshActivityViaPauseForCameraCompat();
+
+        mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+        assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
+    }
+
+    @Test
+    public void testOnActivityRefreshed_setIsRefreshRequestedToFalse() throws Exception {
+        configureActivityAndDisplay();
+        mActivityRefresher.addEvaluator(mEvaluatorTrue);
+        doReturn(true).when(mActivity.mLetterboxUiController)
+                .shouldRefreshActivityViaPauseForCameraCompat();
+
+        mActivityRefresher.onActivityRefreshed(mActivity);
+
+        assertActivityRefreshRequested(false);
+    }
+
+    private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception {
+        assertActivityRefreshRequested(refreshRequested, /* cycleThroughStop*/ true);
+    }
+
+    private void assertActivityRefreshRequested(boolean refreshRequested,
+            boolean cycleThroughStop) throws Exception {
+        verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0))
+                .setIsRefreshRequested(true);
+
+        final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
+                cycleThroughStop ? ON_STOP : ON_PAUSE);
+        final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(mActivity.token,
+                /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
+
+        verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
+                .scheduleTransactionAndLifecycleItems(mActivity.app.getThread(),
+                        refreshCallbackItem, resumeActivityItem);
+    }
+
+    private void configureActivityAndDisplay() {
+        mActivity = new TaskBuilder(mSupervisor)
+                .setCreateActivity(true)
+                .setDisplay(mDisplayContent)
+                // Set the component to be that of the test class in order to enable compat changes
+                .setComponent(ComponentName.createRelative(mContext,
+                        ActivityRefresherTests.class.getName()))
+                .setWindowingMode(WINDOWING_MODE_FREEFORM)
+                .build()
+                .getTopMostActivity();
+
+        spyOn(mActivity.mLetterboxUiController);
+        doReturn(true).when(
+                mActivity.mLetterboxUiController).shouldRefreshActivityForCameraCompat();
+
+        doReturn(true).when(mActivity).inFreeformWindowingMode();
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 10eae57..1355092 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -46,7 +46,6 @@
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Process.SYSTEM_UID;
-import static android.server.wm.ActivityManagerTestBase.isTablet;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
@@ -76,7 +75,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -124,7 +122,6 @@
 import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
 import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
 import com.android.server.wm.utils.MockTracker;
-import com.android.window.flags.Flags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -1298,12 +1295,6 @@
      */
     @Test
     public void testDeliverIntentToTopActivityOfNonTopDisplay() {
-        // TODO(b/330152508): Remove check once legacy multi-display behaviour can coexist with
-        //  desktop windowing mode
-        // Ignore test if desktop windowing is enabled on tablets as legacy multi-display
-        // behaviour will not be respected
-        assumeFalse(Flags.enableDesktopWindowingMode() && isTablet());
-
         final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK,
                 false /* mockGetRootTask */);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
index 695faa5..39a2259 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
@@ -19,6 +19,8 @@
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PENDING_INTENT;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK;
+import static com.android.window.flags.Flags.balImprovedMetrics;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -145,6 +147,16 @@
         }
 
         @Override
+        boolean shouldLogStats(BalVerdict finalVerdict, BalState state) {
+            return true;
+        }
+
+        @Override
+        boolean shouldLogIntentActivity(BalVerdict finalVerdict, BalState state) {
+            return true;
+        }
+
+        @Override
         BalVerdict checkBackgroundActivityStartAllowedByCaller(BalState state) {
             return mCallerVerdict.orElseGet(
                     () -> super.checkBackgroundActivityStartAllowedByCaller(state));
@@ -238,7 +250,12 @@
 
         // assertions
         assertThat(verdict.getCode()).isEqualTo(BackgroundActivityStartController.BAL_BLOCK);
-        assertThat(mBalAllowedLogs).isEmpty(); // not allowed
+        if (balImprovedMetrics()) {
+            assertThat(mBalAllowedLogs).containsExactly(
+                    new BalAllowedLog("package.app3/someClass", BAL_BLOCK));
+        } else {
+            assertThat(mBalAllowedLogs).isEmpty(); // not allowed
+        }
     }
 
     // Tests for BackgroundActivityStartController.checkBackgroundActivityStart
@@ -268,7 +285,12 @@
 
         // assertions
         assertThat(verdict).isEqualTo(BalVerdict.BLOCK);
-        assertThat(mBalAllowedLogs).isEmpty(); // not allowed
+        if (balImprovedMetrics()) {
+            assertThat(mBalAllowedLogs).containsExactly(
+                    new BalAllowedLog("package.app3/someClass", BAL_BLOCK));
+        } else {
+            assertThat(mBalAllowedLogs).isEmpty(); // not allowed
+        }
     }
 
     @Test
@@ -298,7 +320,12 @@
 
         // assertions
         assertThat(verdict).isEqualTo(callerVerdict);
-        assertThat(mBalAllowedLogs).isEmpty(); // non-critical exception
+        if (balImprovedMetrics()) {
+            assertThat(mBalAllowedLogs).containsExactly(
+                    new BalAllowedLog("package.app3/someClass", callerVerdict.getCode()));
+        } else {
+            assertThat(mBalAllowedLogs).isEmpty(); // non-critical exception
+        }
     }
 
     @Test
@@ -362,7 +389,13 @@
 
         // assertions
         assertThat(verdict).isEqualTo(callerVerdict);
-        assertThat(mBalAllowedLogs).containsExactly(new BalAllowedLog("", callerVerdict.getCode()));
+        if (balImprovedMetrics()) {
+            assertThat(mBalAllowedLogs).containsExactly(
+                    new BalAllowedLog("package.app3/someClass", callerVerdict.getCode()));
+        } else {
+            assertThat(mBalAllowedLogs).containsExactly(
+                    new BalAllowedLog("", callerVerdict.getCode()));
+        }
     }
 
     @Test
@@ -398,7 +431,12 @@
 
         // assertions
         assertThat(verdict).isEqualTo(BalVerdict.BLOCK);
-        assertThat(mBalAllowedLogs).isEmpty();
+        if (balImprovedMetrics()) {
+            assertThat(mBalAllowedLogs).containsExactly(
+                    new BalAllowedLog("package.app3/someClass", BAL_BLOCK));
+        } else {
+            assertThat(mBalAllowedLogs).isEmpty();
+        }
     }
 
     @Test
@@ -430,7 +468,12 @@
 
         // assertions
         assertThat(verdict).isEqualTo(callerVerdict);
-        assertThat(mBalAllowedLogs).isEmpty();
+        if (balImprovedMetrics()) {
+            assertThat(mBalAllowedLogs).containsExactly(
+                    new BalAllowedLog("package.app3/someClass", callerVerdict.getCode()));
+        } else {
+            assertThat(mBalAllowedLogs).isEmpty();
+        }
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index f92387c..a268aa9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -79,19 +79,19 @@
 
     @Test
     @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-    public void testReturnsContinueIfDesktopWindowingIsDisabled() {
+    public void testReturnsSkipIfDesktopWindowingIsDisabled() {
         setupDesktopModeLaunchParamsModifier();
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(null).calculate());
+        assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(null).calculate());
     }
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-    public void testReturnsContinueIfDesktopWindowingIsEnabledOnUnsupportedDevice() {
+    public void testReturnsSkipIfDesktopWindowingIsEnabledOnUnsupportedDevice() {
         setupDesktopModeLaunchParamsModifier(/*isDesktopModeSupported=*/ false,
                 /*enforceDeviceRestrictions=*/ true);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(null).calculate());
+        assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(null).calculate());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 44d1b54..87395a1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -113,15 +113,10 @@
 import android.metrics.LogMaker;
 import android.os.Binder;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.util.ArraySet;
-import android.util.DisplayMetrics;
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
@@ -131,7 +126,6 @@
 import android.view.ISystemGestureExclusionListener;
 import android.view.IWindowManager;
 import android.view.InsetsState;
-import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
@@ -151,7 +145,6 @@
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.utils.WmDisplayCutout;
 
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -178,10 +171,6 @@
 @RunWith(WindowTestRunner.class)
 public class DisplayContentTests extends WindowTestsBase {
 
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule =
-            DeviceFlagsValueProvider.createCheckFlagsRule();
-
     @SetupWindows(addAllCommonWindows = true)
     @Test
     public void testForAllWindows() {
@@ -514,44 +503,6 @@
         assertEquals(currentConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
     }
 
-    /**
-     * Tests tapping on a root task in different display results in window gaining focus.
-     */
-    @Test
-    @RequiresFlagsDisabled(com.android.input.flags.Flags.FLAG_REMOVE_POINTER_EVENT_TRACKING_IN_WM)
-    public void testInputEventBringsCorrectDisplayInFocus() {
-        DisplayContent dc0 = mWm.getDefaultDisplayContentLocked();
-        // Create a second display
-        final DisplayContent dc1 = createNewDisplay();
-
-        // Add root task with activity.
-        final Task rootTask0 = createTask(dc0);
-        final Task task0 = createTaskInRootTask(rootTask0, 0 /* userId */);
-        final ActivityRecord activity = createNonAttachedActivityRecord(dc0);
-        task0.addChild(activity, 0);
-        dc0.configureDisplayPolicy();
-        assertNotNull(dc0.mTapDetector);
-
-        final Task rootTask1 = createTask(dc1);
-        final Task task1 = createTaskInRootTask(rootTask1, 0 /* userId */);
-        final ActivityRecord activity1 = createNonAttachedActivityRecord(dc0);
-        task1.addChild(activity1, 0);
-        dc1.configureDisplayPolicy();
-        assertNotNull(dc1.mTapDetector);
-
-        // tap on primary display.
-        tapOnDisplay(dc0);
-        // Check focus is on primary display.
-        assertEquals(mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus,
-                dc0.findFocusedWindow());
-
-        // Tap on secondary display.
-        tapOnDisplay(dc1);
-        // Check focus is on secondary.
-        assertEquals(mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus,
-                dc1.findFocusedWindow());
-    }
-
     @Test
     public void testFocusedWindowMultipleDisplays() {
         doTestFocusedWindowMultipleDisplays(false /* perDisplayFocusEnabled */, Q);
@@ -2959,33 +2910,4 @@
             throw new RuntimeException(e);
         }
     }
-
-    private void tapOnDisplay(final DisplayContent dc) {
-        final DisplayMetrics dm = dc.getDisplayMetrics();
-        final float x = dm.widthPixels / 2;
-        final float y = dm.heightPixels / 2;
-        final long downTime = SystemClock.uptimeMillis();
-        final long eventTime = SystemClock.uptimeMillis() + 100;
-        // sending ACTION_DOWN
-        final MotionEvent downEvent = MotionEvent.obtain(
-                downTime,
-                downTime,
-                MotionEvent.ACTION_DOWN,
-                x,
-                y,
-                0 /*metaState*/);
-        downEvent.setDisplayId(dc.getDisplayId());
-        dc.mTapDetector.onPointerEvent(downEvent);
-
-        // sending ACTION_UP
-        final MotionEvent upEvent = MotionEvent.obtain(
-                downTime,
-                eventTime,
-                MotionEvent.ACTION_UP,
-                x,
-                y,
-                0 /*metaState*/);
-        upEvent.setDisplayId(dc.getDisplayId());
-        dc.mTapDetector.onPointerEvent(upEvent);
-    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 262ba8b..c76acd7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -91,6 +91,7 @@
     private CameraManager mMockCameraManager;
     private Handler mMockHandler;
     private LetterboxConfiguration mLetterboxConfiguration;
+    private ActivityRefresher mActivityRefresher;
 
     private DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
     private CameraManager.AvailabilityCallback mCameraAvailabilityCallback;
@@ -132,8 +133,9 @@
                 });
         CameraStateMonitor cameraStateMonitor =
                 new CameraStateMonitor(mDisplayContent, mMockHandler);
-        mDisplayRotationCompatPolicy =
-                new DisplayRotationCompatPolicy(mDisplayContent, mMockHandler, cameraStateMonitor);
+        mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mMockHandler);
+        mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(mDisplayContent,
+                cameraStateMonitor, mActivityRefresher);
 
         // Do not show the real toast.
         spyOn(mDisplayRotationCompatPolicy);
@@ -606,7 +608,7 @@
     private void assertActivityRefreshRequested(boolean refreshRequested,
                 boolean cycleThroughStop) throws Exception {
         verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0))
-                .setIsRefreshAfterRotationRequested(true);
+                .setIsRefreshRequested(true);
 
         final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
                 cycleThroughStop ? ON_STOP : ON_PAUSE);
@@ -628,7 +630,7 @@
 
     private void callOnActivityConfigurationChanging(
             ActivityRecord activity, boolean isDisplayRotationChanging) {
-        mDisplayRotationCompatPolicy.onActivityConfigurationChanging(activity,
+        mActivityRefresher.onActivityConfigurationChanging(activity,
                 /* newConfig */ createConfigurationWithDisplayRotation(ROTATION_0),
                 /* newConfig */ createConfigurationWithDisplayRotation(
                         isDisplayRotationChanging ? ROTATION_90 : ROTATION_0));
diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index 7380aec..d8d5729 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -65,7 +65,7 @@
         performSurfacePlacementAndWaitForWindowAnimator();
 
         mImeProvider.scheduleShowImePostLayout(appWin, ImeTracker.Token.empty());
-        assertTrue(mImeProvider.isReadyToShowIme());
+        assertTrue(mImeProvider.isScheduledAndReadyToShowIme());
     }
 
     /**
@@ -84,13 +84,13 @@
 
         // Schedule (without triggering) after everything is ready.
         mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty());
-        assertTrue(mImeProvider.isReadyToShowIme());
+        assertTrue(mImeProvider.isScheduledAndReadyToShowIme());
         assertFalse(mImeProvider.isImeShowing());
 
         // Manually trigger the show.
-        mImeProvider.checkShowImePostLayout();
-        // No longer ready as it was already shown.
-        assertFalse(mImeProvider.isReadyToShowIme());
+        mImeProvider.checkAndStartShowImePostLayout();
+        // No longer scheduled as it was already shown.
+        assertFalse(mImeProvider.isScheduledAndReadyToShowIme());
         assertTrue(mImeProvider.isImeShowing());
     }
 
@@ -104,7 +104,7 @@
 
         // Schedule before anything is ready.
         mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty());
-        assertFalse(mImeProvider.isReadyToShowIme());
+        assertFalse(mImeProvider.isScheduledAndReadyToShowIme());
         assertFalse(mImeProvider.isImeShowing());
 
         final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
@@ -115,8 +115,8 @@
         mDisplayContent.updateImeInputAndControlTarget(target);
         // Performing surface placement picks up the show scheduled above.
         performSurfacePlacementAndWaitForWindowAnimator();
-        // No longer ready as it was already shown.
-        assertFalse(mImeProvider.isReadyToShowIme());
+        // No longer scheduled as it was already shown.
+        assertFalse(mImeProvider.isScheduledAndReadyToShowIme());
         assertTrue(mImeProvider.isImeShowing());
     }
 
@@ -137,19 +137,19 @@
 
         // Schedule before starting the afterPrepareSurfacesRunnable.
         mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty());
-        assertFalse(mImeProvider.isReadyToShowIme());
+        assertFalse(mImeProvider.isScheduledAndReadyToShowIme());
         assertFalse(mImeProvider.isImeShowing());
 
         // This tries to pick up the show scheduled above, but must fail as the
         // afterPrepareSurfacesRunnable was not started yet.
         mDisplayContent.applySurfaceChangesTransaction();
-        assertFalse(mImeProvider.isReadyToShowIme());
+        assertFalse(mImeProvider.isScheduledAndReadyToShowIme());
         assertFalse(mImeProvider.isImeShowing());
 
         // Starting the afterPrepareSurfacesRunnable picks up the show scheduled above.
         mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
-        // No longer ready as it was already shown.
-        assertFalse(mImeProvider.isReadyToShowIme());
+        // No longer scheduled as it was already shown.
+        assertFalse(mImeProvider.isScheduledAndReadyToShowIme());
         assertTrue(mImeProvider.isImeShowing());
     }
 
@@ -169,7 +169,7 @@
 
         // Schedule before surface placement.
         mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty());
-        assertFalse(mImeProvider.isReadyToShowIme());
+        assertFalse(mImeProvider.isScheduledAndReadyToShowIme());
         assertFalse(mImeProvider.isImeShowing());
 
         // Performing surface placement picks up the show scheduled above, and succeeds.
@@ -177,8 +177,8 @@
         // applySurfaceChangesTransaction. Both of them try to trigger the show,
         // but only the second one can succeed, as it comes after onPostLayout.
         performSurfacePlacementAndWaitForWindowAnimator();
-        // No longer ready as it was already shown.
-        assertFalse(mImeProvider.isReadyToShowIme());
+        // No longer scheduled as it was already shown.
+        assertFalse(mImeProvider.isScheduledAndReadyToShowIme());
         assertTrue(mImeProvider.isImeShowing());
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 0e1a1af..c69faed 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -353,6 +353,17 @@
     }
 
     @Test
+    public void testControlTargetChangedWhileProviderHasNoWindow() {
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        final InsetsSourceProvider provider = getController().getOrCreateSourceProvider(
+                ID_STATUS_BAR, statusBars());
+        getController().onBarControlTargetChanged(app, null, null, null);
+        assertNull(getController().getControlsForDispatch(app));
+        provider.setWindowContainer(createWindow(null, TYPE_APPLICATION, "statusBar"), null, null);
+        assertNotNull(getController().getControlsForDispatch(app));
+    }
+
+    @Test
     public void testTransientVisibilityOfFixedRotationState() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
index b90fa21..79e401c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -18,15 +18,17 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
-
 import static com.android.server.wm.testing.Assert.assertThrows;
 
+import static junit.framework.Assert.assertEquals;
+
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -35,11 +37,13 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.platform.test.annotations.Presubmit;
+import android.util.DisplayMetrics;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.server.wm.testing.Assert;
+import com.android.internal.R;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -277,7 +281,7 @@
     }
 
     @Test
-    public void test_lettterboxPositionWhenReachabilityEnabledIsSet() {
+    public void test_letterboxPositionWhenReachabilityEnabledIsSet() {
         // Check that horizontal reachability is set with correct arguments
         mLetterboxConfiguration.setPersistentLetterboxPositionForHorizontalReachability(
                 false /* forBookMode */, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
@@ -344,4 +348,48 @@
         mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(0.5f);
         mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(1);
     }
+
+    @Test
+    public void test_evaluateThinLetterboxWhenDensityChanges() {
+        final Resources rs = mock(Resources.class);
+        final DisplayMetrics dm = mock(DisplayMetrics.class);
+        final LetterboxConfigurationPersister lp = mock(LetterboxConfigurationPersister.class);
+        spyOn(mContext);
+        when(rs.getDisplayMetrics()).thenReturn(dm);
+        when(mContext.getResources()).thenReturn(rs);
+        when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxWidthDp))
+                .thenReturn(100);
+        when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxHeightDp))
+                .thenReturn(200);
+        final LetterboxConfiguration configuration = new LetterboxConfiguration(mContext, lp);
+
+        // Verify the values are the expected ones
+        dm.density = 100;
+        when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxWidthDp))
+                .thenReturn(100);
+        when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxHeightDp))
+                .thenReturn(200);
+        final int thinWidthPx = configuration.getThinLetterboxWidthPx();
+        final int thinHeightPx = configuration.getThinLetterboxHeightPx();
+        assertEquals(100, thinWidthPx);
+        assertEquals(200, thinHeightPx);
+
+        // We change the values in the resources but not the update condition (density) and the
+        // result should not change
+        when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxWidthDp))
+                .thenReturn(300);
+        when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxHeightDp))
+                .thenReturn(400);
+        final int thinWidthPx2 = configuration.getThinLetterboxWidthPx();
+        final int thinHeightPx2 = configuration.getThinLetterboxHeightPx();
+        assertEquals(100, thinWidthPx2);
+        assertEquals(200, thinHeightPx2);
+
+        // We update the condition (density) so the new resource values should be read
+        dm.density = 150;
+        final int thinWidthPx3 = configuration.getThinLetterboxWidthPx();
+        final int thinHeightPx3 = configuration.getThinLetterboxHeightPx();
+        assertEquals(300, thinWidthPx3);
+        assertEquals(400, thinHeightPx3);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index b74da1a..6b17de4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -85,6 +85,8 @@
 import android.content.pm.PackageManager.Property;
 import android.content.res.Resources;
 import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.view.InsetsSource;
 import android.view.InsetsState;
@@ -96,6 +98,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.window.flags.Flags;
 
 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -1528,6 +1531,104 @@
                         mActivity.getParent().getConfiguration()), /* delta */  0.01);
     }
 
+    @Test
+    public void testIsVerticalThinLetterboxed() {
+        // Vertical thin letterbox disabled
+        doReturn(-1).when(mActivity.mWmService.mLetterboxConfiguration)
+                .getThinLetterboxHeightPx();
+        assertFalse(mController.isVerticalThinLetterboxed());
+        // Define a Task 100x100
+        final Task task = mock(Task.class);
+        doReturn(new Rect(0, 0, 100, 100)).when(task).getBounds();
+        doReturn(10).when(mActivity.mWmService.mLetterboxConfiguration)
+                .getThinLetterboxHeightPx();
+
+        // Vertical thin letterbox disabled without Task
+        doReturn(null).when(mActivity).getTask();
+        assertFalse(mController.isVerticalThinLetterboxed());
+        // Assign a Task for the Activity
+        doReturn(task).when(mActivity).getTask();
+
+        // (task.width() - act.width()) / 2  = 5 < 10
+        doReturn(new Rect(5, 5, 95, 95)).when(mActivity).getBounds();
+        assertTrue(mController.isVerticalThinLetterboxed());
+
+        // (task.width() - act.width()) / 2  = 10 = 10
+        doReturn(new Rect(10, 10, 90, 90)).when(mActivity).getBounds();
+        assertTrue(mController.isVerticalThinLetterboxed());
+
+        // (task.width() - act.width()) / 2  = 11 > 10
+        doReturn(new Rect(11, 11, 89, 89)).when(mActivity).getBounds();
+        assertFalse(mController.isVerticalThinLetterboxed());
+    }
+
+    @Test
+    public void testIsHorizontalThinLetterboxed() {
+        // Horizontal thin letterbox disabled
+        doReturn(-1).when(mActivity.mWmService.mLetterboxConfiguration)
+                .getThinLetterboxWidthPx();
+        assertFalse(mController.isHorizontalThinLetterboxed());
+        // Define a Task 100x100
+        final Task task = mock(Task.class);
+        doReturn(new Rect(0, 0, 100, 100)).when(task).getBounds();
+        doReturn(10).when(mActivity.mWmService.mLetterboxConfiguration)
+                .getThinLetterboxWidthPx();
+
+        // Vertical thin letterbox disabled without Task
+        doReturn(null).when(mActivity).getTask();
+        assertFalse(mController.isHorizontalThinLetterboxed());
+        // Assign a Task for the Activity
+        doReturn(task).when(mActivity).getTask();
+
+        // (task.height() - act.height()) / 2  = 5 < 10
+        doReturn(new Rect(5, 5, 95, 95)).when(mActivity).getBounds();
+        assertTrue(mController.isHorizontalThinLetterboxed());
+
+        // (task.height() - act.height()) / 2  = 10 = 10
+        doReturn(new Rect(10, 10, 90, 90)).when(mActivity).getBounds();
+        assertTrue(mController.isHorizontalThinLetterboxed());
+
+        // (task.height() - act.height()) / 2  = 11 > 10
+        doReturn(new Rect(11, 11, 89, 89)).when(mActivity).getBounds();
+        assertFalse(mController.isHorizontalThinLetterboxed());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
+    public void testAllowReachabilityForThinLetterboxWithFlagEnabled() {
+        spyOn(mController);
+        doReturn(true).when(mController).isVerticalThinLetterboxed();
+        assertFalse(mController.allowVerticalReachabilityForThinLetterbox());
+        doReturn(true).when(mController).isHorizontalThinLetterboxed();
+        assertFalse(mController.allowHorizontalReachabilityForThinLetterbox());
+
+        doReturn(false).when(mController).isVerticalThinLetterboxed();
+        assertTrue(mController.allowVerticalReachabilityForThinLetterbox());
+        doReturn(false).when(mController).isHorizontalThinLetterboxed();
+        assertTrue(mController.allowHorizontalReachabilityForThinLetterbox());
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
+    public void testAllowReachabilityForThinLetterboxWithFlagDisabled() {
+        spyOn(mController);
+        doReturn(true).when(mController).isVerticalThinLetterboxed();
+        assertTrue(mController.allowVerticalReachabilityForThinLetterbox());
+        doReturn(true).when(mController).isHorizontalThinLetterboxed();
+        assertTrue(mController.allowHorizontalReachabilityForThinLetterbox());
+
+        doReturn(false).when(mController).isVerticalThinLetterboxed();
+        assertTrue(mController.allowVerticalReachabilityForThinLetterbox());
+        doReturn(false).when(mController).isHorizontalThinLetterboxed();
+        assertTrue(mController.allowHorizontalReachabilityForThinLetterbox());
+    }
+
+    @Test
+    public void testIsLetterboxEducationEnabled() {
+        mController.isLetterboxEducationEnabled();
+        verify(mLetterboxConfiguration).getIsEducationEnabled();
+    }
+
     private void mockThatProperty(String propertyName, boolean value) throws Exception {
         Property property = new Property(propertyName, /* value */ value, /* packageName */ "",
                 /* className */ "");
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 75b84d1..6ec1429 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1373,26 +1373,6 @@
         assertTrue(info.supportsMultiWindow);
     }
 
-    @Test
-    public void testRemoveCompatibleRecentTask() {
-        final Task task1 = createTaskBuilder(".Task").setWindowingMode(
-                WINDOWING_MODE_FULLSCREEN).build();
-        mRecentTasks.add(task1);
-        final Task task2 = createTaskBuilder(".Task").setWindowingMode(
-                WINDOWING_MODE_MULTI_WINDOW).build();
-        mRecentTasks.add(task2);
-        assertEquals(2, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
-                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size());
-
-        // Set windowing mode and ensure the same fullscreen task that created earlier is removed.
-        task2.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
-        mRecentTasks.removeCompatibleRecentTask(task2);
-        assertEquals(1, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
-                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size());
-        assertEquals(task2.mTaskId, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
-                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().get(0).taskId);
-    }
-
     private TaskSnapshot createSnapshot(Point taskSize, Point bufferSize) {
         HardwareBuffer buffer = null;
         if (bufferSize != null) {
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 8677738..6b605ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -3646,11 +3646,27 @@
     }
 
     @Test
+    public void testIsReachabilityEnabled_thisLetterbox_false() {
+        // Case when the reachability would be enabled otherwise
+        setUpDisplaySizeWithApp(/* dw */ 1000, /* dh */ 2800);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true);
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+        mActivity.getWindowConfiguration().setBounds(null);
+
+        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ false);
+
+        assertFalse(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
+        assertFalse(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+    }
+
+    @Test
     public void testIsHorizontalReachabilityEnabled_splitScreen_false() {
         mAtm.mDevEnableNonResizableMultiWindow = true;
         setUpDisplaySizeWithApp(2800, 1000);
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true);
+        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);
         final TestSplitOrganizer organizer =
                 new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
 
@@ -3673,6 +3689,7 @@
         setUpDisplaySizeWithApp(1000, 2800);
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true);
+        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);
         final TestSplitOrganizer organizer =
                 new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
 
@@ -3694,6 +3711,7 @@
         setUpDisplaySizeWithApp(1000, 2800);
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true);
+        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);
 
         // Unresizable landscape-only activity.
         prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_LANDSCAPE);
@@ -3715,6 +3733,7 @@
         setUpDisplaySizeWithApp(/* dw */ 1000, /* dh */ 2800);
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true);
+        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);
 
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
 
@@ -3731,6 +3750,7 @@
         setUpDisplaySizeWithApp(/* dw */ 2800, /* dh */ 1000);
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true);
+        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);
 
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
 
@@ -3747,6 +3767,7 @@
         // Portrait display
         setUpDisplaySizeWithApp(1400, 1600);
         mActivity.mWmService.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true);
+        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);
 
         // 16:9f unresizable portrait app
         prepareMinAspectRatio(mActivity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
@@ -3760,6 +3781,7 @@
         // Landscape display
         setUpDisplaySizeWithApp(1600, 1500);
         mActivity.mWmService.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true);
+        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);
 
         // 16:9f unresizable landscape app
         prepareMinAspectRatio(mActivity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
@@ -3773,6 +3795,7 @@
         setUpDisplaySizeWithApp(2800, 1000);
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true);
+        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);
 
         // Unresizable portrait-only activity.
         prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_PORTRAIT);
@@ -3794,6 +3817,7 @@
         setUpDisplaySizeWithApp(1800, 2200);
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true);
+        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);
 
         // Unresizable portrait-only activity.
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
@@ -3815,6 +3839,7 @@
         setUpDisplaySizeWithApp(2200, 1800);
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true);
+        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);
 
         // Unresizable landscape-only activity.
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
@@ -5011,6 +5036,14 @@
         assertFalse(activity.shouldSendCompatFakeFocus());
     }
 
+    private void setUpAllowThinLetterboxed(boolean thinLetterboxAllowed) {
+        spyOn(mActivity.mLetterboxUiController);
+        doReturn(thinLetterboxAllowed).when(mActivity.mLetterboxUiController)
+                .allowVerticalReachabilityForThinLetterbox();
+        doReturn(thinLetterboxAllowed).when(mActivity.mLetterboxUiController)
+                .allowHorizontalReachabilityForThinLetterbox();
+    }
+
     private int getExpectedSplitSize(int dimensionToSplit) {
         int dividerWindowWidth =
                 mActivity.mWmService.mContext.getResources().getDimensionPixelSize(
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 83e4151..d9fd312 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -487,6 +487,16 @@
         // Flush EVENT_APPEARED.
         mController.dispatchPendingEvents();
 
+        // Even if the activity is not launched in an organized TaskFragment, it is still considered
+        // as the remote activity to the organizer process. Because when the task becomes visible,
+        // the organizer process needs to be interactive (unfrozen) to receive TaskFragment events.
+        activity.setVisibleRequested(true);
+        activity.setState(ActivityRecord.State.RESUMED, "test");
+        assertTrue(organizerProc.hasVisibleActivities());
+        activity.setVisibleRequested(false);
+        activity.setState(ActivityRecord.State.STOPPED, "test");
+        assertFalse(organizerProc.hasVisibleActivities());
+
         // Make sure the activity belongs to the same app, but it is in a different pid.
         activity.info.applicationInfo.uid = uid;
         doReturn(pid + 1).when(activity).getPid();
@@ -1884,11 +1894,7 @@
     public void testApplyTransaction_createTaskFragmentDecorSurface() {
         mSetFlagsRule.enableFlags(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG);
 
-        // TODO(b/293654166) remove system organizer requirement once security review is cleared.
-        mController.unregisterOrganizer(mIOrganizer);
-        registerTaskFragmentOrganizer(mIOrganizer, true /* isSystemOrganizer */);
         final Task task = createTask(mDisplayContent);
-
         final TaskFragment tf = createTaskFragment(task);
         final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
                 OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE).build();
@@ -1903,9 +1909,6 @@
     public void testApplyTransaction_removeTaskFragmentDecorSurface() {
         mSetFlagsRule.enableFlags(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG);
 
-        // TODO(b/293654166) remove system organizer requirement once security review is cleared.
-        mController.unregisterOrganizer(mIOrganizer);
-        registerTaskFragmentOrganizer(mIOrganizer, true /* isSystemOrganizer */);
         final Task task = createTask(mDisplayContent);
         final TaskFragment tf = createTaskFragment(task);
 
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 a90a158..d57a7e6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE;
 import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -961,6 +962,22 @@
         assertEquals(appLeftTop, task.getDisplayContent().mFocusedApp);
     }
 
+    @Test
+    public void testShouldBeVisible_invisibleForEmptyTaskFragment() {
+        final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .build();
+
+        // Empty taskFragment should be invisible
+        assertFalse(taskFragment.shouldBeVisible(null));
+
+        // Should be invisible even if it is ACTIVITY_TYPE_HOME.
+        when(taskFragment.getActivityType()).thenReturn(ACTIVITY_TYPE_HOME);
+        assertFalse(taskFragment.shouldBeVisible(null));
+    }
+
     private WindowState createAppWindow(ActivityRecord app, String name) {
         final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, app, name,
                 0 /* ownerId */, false /* ownerCanAddInternalSystemWindow */, new TestIWindow());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index 37de51e..4fc222b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -95,10 +95,6 @@
     }
 
     @Override
-    public void updatePointerIcon(float x, float y) throws RemoteException {
-    }
-
-    @Override
     public void dispatchWindowShown() throws RemoteException {
     }
 
@@ -128,4 +124,9 @@
     public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken)
             throws RemoteException {
     }
+
+    @Override
+    public void dumpWindow(ParcelFileDescriptor pfd) {
+
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index fbbb9a2..b152c3e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -819,17 +819,20 @@
         assertFalse(win.getOrientationChanging());
     }
 
-    @SetupWindows(addWindows = W_ABOVE_ACTIVITY)
     @Test
     public void testRequestResizeForBlastSync() {
-        final WindowState win = mChildAppWindowAbove;
-        makeWindowVisible(win, win.getParentWindow());
+        final WindowState win = createWindow(null, TYPE_APPLICATION, "window");
+        makeWindowVisible(win);
+        makeLastConfigReportedToClient(win, true /* visible */);
         win.mLayoutSeq = win.getDisplayContent().mLayoutSeq;
         win.reportResized();
         win.updateResizingWindowIfNeeded();
         assertThat(mWm.mResizingWindows).doesNotContain(win);
 
         // Check that the window is in resizing if using blast sync.
+        final BLASTSyncEngine.SyncGroup syncGroup = mock(BLASTSyncEngine.SyncGroup.class);
+        syncGroup.mSyncMethod = BLASTSyncEngine.METHOD_BLAST;
+        win.mSyncGroup = syncGroup;
         win.reportResized();
         win.prepareSync();
         assertEquals(SYNC_STATE_WAITING_FOR_DRAW, win.mSyncState);
@@ -842,6 +845,20 @@
         mWm.mResizingWindows.remove(win);
         win.updateResizingWindowIfNeeded();
         assertThat(mWm.mResizingWindows).doesNotContain(win);
+
+        // Non blast sync doesn't require to force resizing, because it won't use syncSeqId.
+        // And if the window is already drawn, it can report sync finish immediately so that the
+        // sync group won't be blocked.
+        win.finishSync(mTransaction, syncGroup, false /* cancel */);
+        syncGroup.mSyncMethod = BLASTSyncEngine.METHOD_NONE;
+        win.mSyncGroup = syncGroup;
+        win.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
+        win.prepareSync();
+        assertEquals(SYNC_STATE_WAITING_FOR_DRAW, win.mSyncState);
+        win.updateResizingWindowIfNeeded();
+        assertThat(mWm.mResizingWindows).doesNotContain(win);
+        assertTrue(win.isSyncFinished(syncGroup));
+        assertEquals(WindowContainer.SYNC_STATE_READY, win.mSyncState);
     }
 
     @Test
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 9d14290..2e93cba 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -654,7 +654,10 @@
                 }
             } else if (Intent.ACTION_USER_STARTED.equals(action)) {
                 if (userId >= 0) {
-                    mHandler.obtainMessage(MSG_USER_STARTED, userId, 0).sendToTarget();
+                    if (!Flags.disableIdleCheck() || userId > 0) {
+                        // Don't check idle state for USER_SYSTEM during the boot up.
+                        mHandler.obtainMessage(MSG_USER_STARTED, userId, 0).sendToTarget();
+                    }
                 }
             }
         }
@@ -2013,6 +2016,8 @@
                 + ": " + Flags.useParceledList());
         pw.println("    " + Flags.FLAG_FILTER_BASED_EVENT_QUERY_API
                 + ": " + Flags.filterBasedEventQueryApi());
+        pw.println("    " + Flags.FLAG_DISABLE_IDLE_CHECK
+                + ": " + Flags.disableIdleCheck());
 
         final int[] userIds;
         synchronized (mLock) {
diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
index 40537c8..6d53c27 100644
--- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -16,6 +16,8 @@
 
 package com.android.server.usb;
 
+import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
+
 import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
 
 import android.Manifest;
@@ -43,6 +45,7 @@
 import android.os.Environment;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.service.usb.UsbProfileGroupSettingsManagerProto;
 import android.service.usb.UsbSettingsAccessoryPreferenceProto;
 import android.service.usb.UsbSettingsDevicePreferenceProto;
@@ -939,13 +942,24 @@
     }
 
     /**
-     * @return true if any application in foreground have set restrict_usb_overlay_activities as
-     * true in manifest file. The application needs to have MANAGE_USB permission.
+     * @return true if the user has not finished the setup process or if there are any
+     * foreground applications with MANAGE_USB permission and restrict_usb_overlay_activities
+     * enabled in the manifest file.
      */
     private boolean shouldRestrictOverlayActivities() {
 
         if (!Flags.allowRestrictionOfOverlayActivities()) return false;
 
+        if (Settings.Secure.getIntForUser(
+                mContext.getContentResolver(),
+                USER_SETUP_COMPLETE,
+                /* defaultValue= */ 1,
+                UserHandle.CURRENT.getIdentifier())
+                == 0) {
+            Slog.d(TAG, "restricting usb overlay activities as setup is not complete");
+            return true;
+        }
+
         List<ActivityManager.RunningAppProcessInfo> appProcessInfos = mActivityManager
                 .getRunningAppProcesses();
 
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 4c719dd..bc8f65e 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3888,7 +3888,7 @@
 
     /**
      * Whether device resets all of NR timers when device is in a voice call and QOS is established.
-     * The default value is false;
+     * The default value is true;
      *
      * @see #KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING
      * @see #KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING
@@ -10909,7 +10909,7 @@
         sDefaults.putString(KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING, "");
         sDefaults.putInt(KEY_NR_ADVANCED_BANDS_SECONDARY_TIMER_SECONDS_INT, 0);
         sDefaults.putBoolean(KEY_NR_TIMERS_RESET_IF_NON_ENDC_AND_RRC_IDLE_BOOL, false);
-        sDefaults.putBoolean(KEY_NR_TIMERS_RESET_ON_VOICE_QOS_BOOL, false);
+        sDefaults.putBoolean(KEY_NR_TIMERS_RESET_ON_VOICE_QOS_BOOL, true);
         sDefaults.putBoolean(KEY_NR_TIMERS_RESET_ON_PLMN_CHANGE_BOOL, false);
         /* Default value is 1 hour. */
         sDefaults.putLong(KEY_5G_WATCHDOG_TIME_MS_LONG, 3600000);
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index f161f31..0ecafc7 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -1283,6 +1283,8 @@
 
     private static final String JAPAN_ISO_COUNTRY_CODE = "JP";
 
+    private static final String SINGAPORE_ISO_COUNTRY_CODE = "SG";
+
     /**
      * Breaks the given number down and formats it according to the rules
      * for the country the number is from.
@@ -1669,6 +1671,17 @@
                  * dialing format.
                  */
                 result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL);
+            } else if (Flags.removeCountryCodeFromLocalSingaporeCalls() &&
+                    (SINGAPORE_ISO_COUNTRY_CODE.equalsIgnoreCase(defaultCountryIso) &&
+                            pn.getCountryCode() ==
+                                    util.getCountryCodeForRegion(SINGAPORE_ISO_COUNTRY_CODE) &&
+                            (pn.getCountryCodeSource() ==
+                                    PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN))) {
+                /*
+                 * Need to reformat Singaporean phone numbers (when the user is in Singapore)
+                 * with the country code (+65) removed to comply with Singaporean regulations.
+                 */
+                result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL);
             } else {
                 result = util.formatInOriginalFormat(pn, defaultCountryIso);
             }
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 8fe45cb..76b4e005 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -99,18 +99,7 @@
 import java.util.stream.Collectors;
 
 /**
- * Subscription manager provides the mobile subscription information that are associated with the
- * calling user profile {@link UserHandle} for Android SDK 35(V) and above, while Android SDK 34(U)
- * and below can see all subscriptions as it does today.
- *
- * <p>For example, if we have
- * <ul>
- *     <li> Subscription 1 associated with personal profile.
- *     <li> Subscription 2 associated with work profile.
- * </ul>
- * Then for SDK 35+, if the caller identity is personal profile, then
- * {@link #getActiveSubscriptionInfoList} will return subscription 1 only and vice versa.
- *
+ * Subscription manager provides the mobile subscription information.
  */
 @SystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
 @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -1980,17 +1969,7 @@
     }
 
     /**
-     * Get the SubscriptionInfo(s) of the currently active SIM(s) associated with the current caller
-     * user profile {@link UserHandle} for Android SDK 35(V) and above, while Android SDK 34(U)
-     * and below can see all subscriptions as it does today.
-     *
-     * <p>For example, if we have
-     * <ul>
-     *     <li> Subscription 1 associated with personal profile.
-     *     <li> Subscription 2 associated with work profile.
-     * </ul>
-     * Then for SDK 35+, if the caller identity is personal profile, then this will return
-     * subscription 1 only and vice versa.
+     * Get the SubscriptionInfo(s) of the currently active SIM(s).
      *
      * <p> Returned records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by
      * {@link SubscriptionInfo#getSubscriptionId}. Beginning with Android SDK 35, this method will
@@ -2259,9 +2238,7 @@
     }
 
     /**
-     * Get the active subscription count associated with the current caller user profile for
-     * Android SDK 35(V) and above, while Android SDK 34(U) and below can see all subscriptions as
-     * it does today.
+     * Get the active subscription count.
      *
      * @return The current number of active subscriptions.
      *
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 03ba8fa..dbe4f27 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -13114,39 +13114,41 @@
     })
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     public @Nullable ServiceState getServiceState(@IncludeLocationData int includeLocationData) {
-        return getServiceStateForSubscriber(getSubId(),
+        return getServiceStateForSlot(SubscriptionManager.getSlotIndex(getSubId()),
                 includeLocationData != INCLUDE_LOCATION_DATA_FINE,
                 includeLocationData == INCLUDE_LOCATION_DATA_NONE);
     }
 
     /**
-     * Returns the service state information on specified subscription. Callers require
-     * either READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE to retrieve the information.
+     * Returns the service state information on specified SIM slot.
      *
-     * May return {@code null} when the subscription is inactive or when there was an error
+     * May return {@code null} when the {@code slotIndex} is invalid or when there was an error
      * communicating with the phone process.
+     *
+     * @param slotIndex of phone whose service state is returned
      * @param renounceFineLocationAccess Set this to true if the caller would not like to receive
      * location related information which will be sent if the caller already possess
      * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and do not renounce the permission
      * @param renounceCoarseLocationAccess Set this to true if the caller would not like to
      * receive location related information which will be sent if the caller already possess
      * {@link Manifest.permission#ACCESS_COARSE_LOCATION} and do not renounce the permissions.
+     * @return Service state on specified SIM slot.
      */
-    private ServiceState getServiceStateForSubscriber(int subId,
-            boolean renounceFineLocationAccess,
+    private ServiceState getServiceStateForSlot(int slotIndex, boolean renounceFineLocationAccess,
             boolean renounceCoarseLocationAccess) {
         try {
             ITelephony service = getITelephony();
             if (service != null) {
-                return service.getServiceStateForSubscriber(subId, renounceFineLocationAccess,
-                        renounceCoarseLocationAccess, getOpPackageName(), getAttributionTag());
+                return service.getServiceStateForSlot(slotIndex,
+                        renounceFineLocationAccess, renounceCoarseLocationAccess,
+                        getOpPackageName(), getAttributionTag());
             }
         } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelephony#getServiceStateForSubscriber", e);
+            Log.e(TAG, "Error calling ITelephony#getServiceStateForSlot", e);
         } catch (NullPointerException e) {
             AnomalyReporter.reportAnomaly(
                     UUID.fromString("e2bed88e-def9-476e-bd71-3e572a8de6d1"),
-                    "getServiceStateForSubscriber " + subId + " NPE");
+                    "getServiceStateForSlot " + slotIndex + " NPE");
         }
         return null;
     }
@@ -13161,7 +13163,35 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public ServiceState getServiceStateForSubscriber(int subId) {
-        return getServiceStateForSubscriber(subId, false, false);
+        return getServiceStateForSlot(
+                SubscriptionManager.getSlotIndex(subId), false, false);
+    }
+
+    /**
+     * Returns the service state information on specified SIM slot.
+     *
+     * If you want continuous updates of service state info, register a {@link TelephonyCallback}
+     * that implements {@link TelephonyCallback.ServiceStateListener} through
+     * {@link #registerTelephonyCallback}.
+     *
+     * May return {@code null} when the {@code slotIndex} is invalid or when there was an error
+     * communicating with the phone process
+     *
+     * See {@link #getActiveModemCount()} to get the total number of slots
+     * that are active on the device.
+     *
+     * @param slotIndex of phone whose service state is returned
+     * @return ServiceState on specified SIM slot.
+     *
+     * @hide
+     */
+    @RequiresPermission(allOf = {
+            Manifest.permission.READ_PHONE_STATE,
+            Manifest.permission.ACCESS_COARSE_LOCATION
+    })
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
+    public @Nullable ServiceState getServiceStateForSlot(int slotIndex) {
+        return getServiceStateForSlot(slotIndex, false, false);
     }
 
     /**
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 0bb5fd5..47f53f3 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -1015,13 +1015,27 @@
      * @hide
      */
     public static final int DATAGRAM_TYPE_KEEP_ALIVE = 3;
+    /**
+     * Datagram type indicating that the datagram to be sent or received is of type SOS message and
+     * is the last message to emergency service provider indicating still needs help.
+     * @hide
+     */
+    public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP = 4;
+    /**
+     * Datagram type indicating that the datagram to be sent or received is of type SOS message and
+     * is the last message to emergency service provider indicating no more help is needed.
+     * @hide
+     */
+    public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED = 5;
 
     /** @hide */
     @IntDef(prefix = "DATAGRAM_TYPE_", value = {
             DATAGRAM_TYPE_UNKNOWN,
             DATAGRAM_TYPE_SOS_MESSAGE,
             DATAGRAM_TYPE_LOCATION_SHARING,
-            DATAGRAM_TYPE_KEEP_ALIVE
+            DATAGRAM_TYPE_KEEP_ALIVE,
+            DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP,
+            DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface DatagramType {}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index d4da736..65de7e4 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1399,19 +1399,18 @@
     oneway void requestModemActivityInfo(in ResultReceiver result);
 
     /**
-     * Get the service state on specified subscription
-     * @param subId Subscription id
+     * Get the service state on specified SIM slot.
+     * @param slotIndex of phone whose service state is returned
      * @param renounceFineLocationAccess Set this to true if the caller would not like to
      * receive fine location related information
      * @param renounceCoarseLocationAccess Set this to true if the caller would not like to
      * receive coarse location related information
      * @param callingPackage The package making the call
      * @param callingFeatureId The feature in the package
-     * @return Service state on specified subscription.
+     * @return Service state on specified SIM slot.
      */
-    ServiceState getServiceStateForSubscriber(int subId, boolean renounceFineLocationAccess,
-            boolean renounceCoarseLocationAccess,
-            String callingPackage, String callingFeatureId);
+    ServiceState getServiceStateForSlot(int slotIndex, boolean renounceFineLocationAccess,
+            boolean renounceCoarseLocationAccess, String callingPackage, String callingFeatureId);
 
     /**
      * Returns the URI for the per-account voicemail ringtone set in Phone settings.
diff --git a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt
index dfbbda6c..afb3593 100644
--- a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt
+++ b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt
@@ -9,21 +9,28 @@
 import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS
 import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE
 import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY
+import android.util.IndentingPrintWriter
+import android.util.Log
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.security.AttestationVerificationManagerService.DumpLogger
 import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
 import java.io.ByteArrayOutputStream
+import java.io.PrintWriter
+import java.io.StringWriter
 import java.security.cert.Certificate
 import java.security.cert.CertificateFactory
 import java.security.cert.TrustAnchor
 import java.security.cert.X509Certificate
 import java.time.LocalDate
+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
+
 
 /** Test for Peer Device attestation verifier. */
 @SmallTest
@@ -31,6 +38,7 @@
 class AttestationVerificationPeerDeviceVerifierTest {
     private val certificateFactory = CertificateFactory.getInstance("X.509")
     @Mock private lateinit var context: Context
+    private val dumpLogger = DumpLogger()
     private lateinit var trustAnchors: HashSet<TrustAnchor>
 
     @Before
@@ -44,37 +52,50 @@
         }
     }
 
+    @After
+    fun dumpAndLog() {
+        val dump = dumpLogger.getDump()
+        Log.d(TAG, "$dump")
+    }
+
     @Test
     fun verifyAttestation_returnsSuccessTypeChallenge() {
         val verifier = AttestationVerificationPeerDeviceVerifier(
-            context, trustAnchors, false, LocalDate.of(2022, 2, 1),
-            LocalDate.of(2021, 8, 1))
+            context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1),
+            LocalDate.of(2021, 8, 1)
+        )
         val challengeRequirements = Bundle()
         challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
 
-        val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
-            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+        val result = verifier.verifyAttestation(
+            TYPE_CHALLENGE, challengeRequirements,
+            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+        )
         assertThat(result).isEqualTo(RESULT_SUCCESS)
     }
 
     @Test
     fun verifyAttestation_returnsSuccessLocalPatchOlderThanOneYear() {
         val verifier = AttestationVerificationPeerDeviceVerifier(
-            context, trustAnchors, false, LocalDate.of(2022, 2, 1),
-            LocalDate.of(2021, 1, 1))
+            context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1),
+            LocalDate.of(2021, 1, 1)
+        )
         val challengeRequirements = Bundle()
         challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
 
-        val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
-            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+        val result = verifier.verifyAttestation(
+            TYPE_CHALLENGE, challengeRequirements,
+            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+        )
         assertThat(result).isEqualTo(RESULT_SUCCESS)
     }
 
     @Test
     fun verifyAttestation_returnsSuccessTypePublicKey() {
         val verifier = AttestationVerificationPeerDeviceVerifier(
-            context, trustAnchors, false, LocalDate.of(2022, 2, 1),
-            LocalDate.of(2021, 8, 1))
+            context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1),
+            LocalDate.of(2021, 8, 1)
+        )
 
         val leafCert =
             (TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToCerts() as List)[0]
@@ -84,61 +105,75 @@
 
         val result = verifier.verifyAttestation(
             TYPE_PUBLIC_KEY, pkRequirements,
-            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+        )
         assertThat(result).isEqualTo(RESULT_SUCCESS)
     }
 
     @Test
     fun verifyAttestation_returnsSuccessOwnedBySystem() {
         val verifier = AttestationVerificationPeerDeviceVerifier(
-                context, trustAnchors, false, LocalDate.of(2022, 2, 1),
-                LocalDate.of(2021, 1, 1))
+            context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1),
+            LocalDate.of(2021, 1, 1)
+        )
         val challengeRequirements = Bundle()
         challengeRequirements.putByteArray(PARAM_CHALLENGE, "activeUnlockValid".encodeToByteArray())
         challengeRequirements.putBoolean("android.key_owned_by_system", true)
 
-        val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
-                TEST_OWNED_BY_SYSTEM_FILENAME.fromPEMFileToByteArray())
+        val result = verifier.verifyAttestation(
+            TYPE_CHALLENGE, challengeRequirements,
+            TEST_OWNED_BY_SYSTEM_FILENAME.fromPEMFileToByteArray()
+        )
+
         assertThat(result).isEqualTo(RESULT_SUCCESS)
     }
 
     @Test
     fun verifyAttestation_returnsFailureOwnedBySystem() {
         val verifier = AttestationVerificationPeerDeviceVerifier(
-                context, trustAnchors, false, LocalDate.of(2022, 2, 1),
-                LocalDate.of(2021, 1, 1))
+            context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1),
+            LocalDate.of(2021, 1, 1)
+        )
         val challengeRequirements = Bundle()
         challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
         challengeRequirements.putBoolean("android.key_owned_by_system", true)
 
-        val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
-                TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+        val result = verifier.verifyAttestation(
+            TYPE_CHALLENGE, challengeRequirements,
+            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+        )
         assertThat(result).isEqualTo(RESULT_FAILURE)
     }
 
     @Test
     fun verifyAttestation_returnsFailurePatchDateNotWithinOneYearLocalPatch() {
         val verifier = AttestationVerificationPeerDeviceVerifier(
-            context, trustAnchors, false, LocalDate.of(2023, 3, 1),
-            LocalDate.of(2023, 2, 1))
+            context, dumpLogger, trustAnchors, false, LocalDate.of(2023, 3, 1),
+            LocalDate.of(2023, 2, 1)
+        )
         val challengeRequirements = Bundle()
         challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
 
-        val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
-            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+        val result = verifier.verifyAttestation(
+            TYPE_CHALLENGE, challengeRequirements,
+            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+        )
         assertThat(result).isEqualTo(RESULT_FAILURE)
     }
 
     @Test
     fun verifyAttestation_returnsFailureTrustedAnchorEmpty() {
         val verifier = AttestationVerificationPeerDeviceVerifier(
-            context, HashSet(), false, LocalDate.of(2022, 1, 1),
-            LocalDate.of(2022, 1, 1))
+            context, dumpLogger, HashSet(), false, LocalDate.of(2022, 1, 1),
+            LocalDate.of(2022, 1, 1)
+        )
         val challengeRequirements = Bundle()
         challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
 
-        val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
-            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+        val result = verifier.verifyAttestation(
+            TYPE_CHALLENGE, challengeRequirements,
+            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+        )
         assertThat(result).isEqualTo(RESULT_FAILURE)
     }
 
@@ -151,32 +186,39 @@
         }
 
         val verifier = AttestationVerificationPeerDeviceVerifier(
-            context, badTrustAnchors, false, LocalDate.of(2022, 1, 1),
-            LocalDate.of(2022, 1, 1))
+            context, dumpLogger, badTrustAnchors, false, LocalDate.of(2022, 1, 1),
+            LocalDate.of(2022, 1, 1)
+        )
         val challengeRequirements = Bundle()
         challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
 
-        val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
-            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+        val result = verifier.verifyAttestation(
+            TYPE_CHALLENGE, challengeRequirements,
+            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+        )
         assertThat(result).isEqualTo(RESULT_FAILURE)
     }
 
     fun verifyAttestation_returnsFailureChallenge() {
         val verifier = AttestationVerificationPeerDeviceVerifier(
-            context, trustAnchors, false, LocalDate.of(2022, 1, 1),
-            LocalDate.of(2022, 1, 1))
+            context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 1, 1),
+            LocalDate.of(2022, 1, 1)
+        )
         val challengeRequirements = Bundle()
         challengeRequirements.putByteArray(PARAM_CHALLENGE, "wrong".encodeToByteArray())
 
-        val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
-            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+        val result = verifier.verifyAttestation(
+            TYPE_CHALLENGE, challengeRequirements,
+            TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+        )
         assertThat(result).isEqualTo(RESULT_FAILURE)
     }
 
     private fun String.fromPEMFileToCerts(): Collection<Certificate> {
         return certificateFactory.generateCertificates(
             InstrumentationRegistry.getInstrumentation().getContext().getResources().getAssets()
-                .open(this))
+                .open(this)
+        )
     }
 
     private fun String.fromPEMFileToByteArray(): ByteArray {
@@ -188,6 +230,12 @@
         return bos.toByteArray()
     }
 
+    private fun DumpLogger.getDump(): String {
+        val sw = StringWriter()
+        this.dumpTo(IndentingPrintWriter(PrintWriter(sw), " "))
+        return sw.toString()
+    }
+
     class TestActivity : Activity() {
         override fun onCreate(savedInstanceState: Bundle?) {
             super.onCreate(savedInstanceState)
@@ -195,6 +243,7 @@
     }
 
     companion object {
+        private const val TAG = "AVFTest"
         private const val TEST_ROOT_CERT_FILENAME = "test_root_certs.pem"
         private const val TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME =
             "test_attestation_with_root_certs.pem"
diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml b/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml
index 6f8f008..955b43a 100644
--- a/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml
+++ b/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml
@@ -19,7 +19,7 @@
           xmlns:tools="http://schemas.android.com/tools"
           package="com.android.server.wm.flicker">
 
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="35"/>
     <!-- Read and write traces from external storage -->
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
@@ -46,6 +46,8 @@
     <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
     <!-- Allow the test to connect to perfetto trace processor -->
     <uses-permission android:name="android.permission.INTERNET"/>
+    <!-- Allow to query for the Launcher TestInfo on SDK 30+ -->
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <!-- Allow the test to write directly to /sdcard/ and connect to trace processor -->
     <application android:requestLegacyExternalStorage="true"
                  android:networkSecurityConfig="@xml/network_security_config"
diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
index 1dc1037..82de070 100644
--- a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
@@ -80,6 +80,7 @@
                 value="trace_config.textproto"
         />
         <option name="instrumentation-arg" key="per_run" value="true"/>
+        <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
     </test>
     <!-- Needed for pulling the collected trace config on to the host -->
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
index cf4edd5..67825d2 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
@@ -43,6 +43,7 @@
  *
  * To run this test: `atest FlickerTestsOther:OpenTrampolineActivityTest`
  */
+@FlakyTest(bugId = 341209752)
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@@ -168,7 +169,6 @@
         }
     }
 
-    @FlakyTest(bugId = 290736037)
     /** Main activity should go from fullscreen to being a split with secondary activity. */
     @Test
     fun mainActivityLayerGoesFromFullscreenToSplit() {
@@ -203,7 +203,6 @@
         }
     }
 
-    @FlakyTest(bugId = 288591571)
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
index bc3696b..eed9225 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
@@ -205,7 +205,8 @@
                         it.visibleRegion(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
                     val secondaryVisibleRegion =
                         it.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
-                    overlayVisibleRegion.coversExactly(secondaryVisibleRegion.region)
+                    // TODO(b/340992001): replace coverAtLeast with coverExactly
+                    overlayVisibleRegion.coversAtLeast(secondaryVisibleRegion.region)
                 }
                 .then()
                 .isInvisible(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
index fb92583..379b45c 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
@@ -60,14 +60,16 @@
             testApp.launchViaIntent(wmHelper)
             testApp.launchSecondaryActivity(wmHelper)
             secondaryApp.launchViaIntent(wmHelper)
-            tapl.goHome()
-            wmHelper
-                .StateSyncBuilder()
-                .withAppTransitionIdle()
-                .withHomeActivityVisible()
-                .waitForAndVerify()
             startDisplayBounds =
                 wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
+
+            // Record the displayBounds before `goHome()` in case the launcher is fixed-portrait.
+            tapl.goHome()
+            wmHelper
+                    .StateSyncBuilder()
+                    .withAppTransitionIdle()
+                    .withHomeActivityVisible()
+                    .waitForAndVerify()
         }
         transitions {
             SplitScreenUtils.enterSplit(
@@ -138,10 +140,6 @@
             check { "ActivityEmbeddingSplitHeight" }
                 .that(leftAELayerRegion.region.bounds.height())
                 .isEqual(rightAELayerRegion.region.bounds.height())
-            check { "SystemSplitHeight" }
-                .that(rightAELayerRegion.region.bounds.height())
-                .isEqual(secondaryAppLayerRegion.region.bounds.height())
-            // TODO(b/292283182): Remove this special case handling.
             check { "ActivityEmbeddingSplitWidth" }
                 .that(
                     abs(
@@ -150,14 +148,6 @@
                     )
                 )
                 .isLower(2)
-            check { "SystemSplitWidth" }
-                .that(
-                    abs(
-                        secondaryAppLayerRegion.region.bounds.width() -
-                            2 * rightAELayerRegion.region.bounds.width()
-                    )
-                )
-                .isLower(2)
         }
     }
 
@@ -170,15 +160,9 @@
                 visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
             val rightAEWindowRegion =
                 visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
-            // There's no window for the divider bar.
-            val secondaryAppLayerRegion =
-                visibleRegion(ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent())
             check { "ActivityEmbeddingSplitHeight" }
                 .that(leftAEWindowRegion.region.bounds.height())
                 .isEqual(rightAEWindowRegion.region.bounds.height())
-            check { "SystemSplitHeight" }
-                .that(rightAEWindowRegion.region.bounds.height())
-                .isEqual(secondaryAppLayerRegion.region.bounds.height())
             check { "ActivityEmbeddingSplitWidth" }
                 .that(
                     abs(
@@ -187,14 +171,6 @@
                     )
                 )
                 .isLower(2)
-            check { "SystemSplitWidth" }
-                .that(
-                    abs(
-                        secondaryAppLayerRegion.region.bounds.width() -
-                            2 * rightAEWindowRegion.region.bounds.width()
-                    )
-                )
-                .isLower(2)
         }
     }
 
diff --git a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
index 57a58c8..4ffb11a 100644
--- a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
@@ -80,6 +80,7 @@
                 value="trace_config.textproto"
         />
         <option name="instrumentation-arg" key="per_run" value="true"/>
+        <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
     </test>
     <!-- Needed for pulling the collected trace config on to the host -->
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
index 2cb86e0..0fa4d07 100644
--- a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
@@ -80,6 +80,7 @@
                 value="trace_config.textproto"
         />
         <option name="instrumentation-arg" key="per_run" value="true"/>
+        <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
     </test>
     <!-- Needed for pulling the collected trace config on to the host -->
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
index 2cf85fa..4d9fefb 100644
--- a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
@@ -80,6 +80,7 @@
                 value="trace_config.textproto"
         />
         <option name="instrumentation-arg" key="per_run" value="true"/>
+        <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
     </test>
     <!-- Needed for pulling the collected trace config on to the host -->
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml
index b93e1be..b879c54 100644
--- a/tests/FlickerTests/IME/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml
@@ -82,6 +82,7 @@
                 value="trace_config.textproto"
         />
         <option name="instrumentation-arg" key="per_run" value="true"/>
+        <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
     </test>
     <!-- Needed for pulling the collected trace config on to the host -->
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/tests/FlickerTests/IME/OWNERS b/tests/FlickerTests/IME/OWNERS
index ae1098d..e3a2e67 100644
--- a/tests/FlickerTests/IME/OWNERS
+++ b/tests/FlickerTests/IME/OWNERS
@@ -1,3 +1,3 @@
 # ime
 # Bug component: 34867
-include /services/core/java/com/android/server/inputmethod/OWNERS
+file:/services/core/java/com/android/server/inputmethod/OWNERS
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
index da8368f..2b6ddcb 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
@@ -32,6 +32,9 @@
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
 
+/**
+ * To run this test: `atest FlickerTestsIme1:CloseImeOnDismissPopupDialogTest`
+ */
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
index 2f3ec63..0344197 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
@@ -33,8 +33,8 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test IME window closing to home transitions. To run this test: `atest
- * FlickerTests:CloseImeWindowToHomeTest`
+ * Test IME window closing to home transitions.
+ * To run this test: `atest FlickerTestsIme1:CloseImeOnGoHomeTest`
  */
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
index 8821b69..fde1373 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
@@ -42,7 +42,7 @@
  *
  * More details on b/190352379
  *
- * To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToHomeTest`
+ * To run this test: `atest FlickerTestsIme1:CloseImeShownOnAppStartOnGoHomeTest`
  */
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
index d75eba6..dc50135 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
@@ -42,7 +42,7 @@
  *
  * More details on b/190352379
  *
- * To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToAppTest`
+ * To run this test: `atest FlickerTestsIme1:CloseImeShownOnAppStartToAppOnPressBackTest`
  */
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
index 41d9e30..dc2bd1b 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
@@ -34,8 +34,8 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test IME window closing back to app window transitions. To run this test: `atest
- * FlickerTests:CloseImeWindowToAppTest`
+ * Test IME window closing back to app window transitions.
+ * To run this test: `atest FlickerTestsIme1:CloseImeToAppOnPressBackTest`
  */
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
index 0e7fb79..05771e8 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
@@ -40,7 +40,7 @@
  * Unlike {@link OpenImeWindowTest} testing IME window opening transitions, this test also verify
  * there is no flickering when back to the simple activity without requesting IME to show.
  *
- * To run this test: `atest FlickerTests:OpenImeWindowAndCloseTest`
+ * To run this test: `atest FlickerTestsIme1:CloseImeToHomeOnFinishActivityTest`
  */
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
index 47a7e1b..336fe6f 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
@@ -36,8 +36,8 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test IME window shown on the app with fixing portrait orientation. To run this test: `atest
- * FlickerTests:OpenImeWindowToFixedPortraitAppTest`
+ * Test IME window shown on the app with fixing portrait orientation.
+ * To run this test: `atest FlickerTestsIme2:OpenImeWindowToFixedPortraitAppTest`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
index 48ec4d1..b8f11dc 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
@@ -38,8 +38,9 @@
 
 /**
  * Test IME window layer will become visible when switching from the fixed orientation activity
- * (e.g. Launcher activity). To run this test: `atest
- * FlickerTests:ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest`
+ * (e.g. Launcher activity).
+ * To run this test:
+ * `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest`
  */
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
index 03f3a68..34a7085 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
@@ -33,7 +33,8 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test IME window opening transitions. To run this test: `atest FlickerTests:ReOpenImeWindowTest`
+ * Test IME window opening transitions.
+ * To run this test: `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppFromOverviewTest`
  */
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
index 7b62c89..7c72c31 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
@@ -35,8 +35,8 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test IME windows switching with 2-Buttons or gestural navigation. To run this test: `atest
- * FlickerTests:SwitchImeWindowsFromGestureNavTest`
+ * Test IME windows switching with 2-Buttons or gestural navigation.
+ * To run this test: `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest`
  */
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
index 53bfb4e..fe5320c 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
@@ -36,7 +36,7 @@
 /**
  * Launch an app that automatically displays the IME
  *
- * To run this test: `atest FlickerTests:LaunchAppShowImeOnStartTest`
+ * To run this test: `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppTest`
  *
  * Actions:
  * ```
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt
index d22bdcf..92b6b93 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt
@@ -35,8 +35,8 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test IME window closing on lock and opening on screen unlock. To run this test: `atest
- * FlickerTests:CloseImeWindowToHomeTest`
+ * Test IME window closing on lock and opening on screen unlock.
+ * To run this test: `atest FlickerTestsIme2:ShowImeOnUnlockScreenTest`
  */
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
index 12290af..9eaf998 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
@@ -31,7 +31,10 @@
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
 
-/** Test IME window opening transitions. To run this test: `atest FlickerTests:OpenImeWindowTest` */
+/**
+ * Test IME window opening transitions.
+ * To run this test: `atest FlickerTestsIme2:ShowImeWhenFocusingOnInputFieldTest`
+ */
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
index 0948351..7186a2c 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
@@ -41,7 +41,7 @@
 
 /**
  * Test IME snapshot mechanism won't apply when transitioning from non-IME focused dialog activity.
- * To run this test: `atest FlickerTests:LaunchAppShowImeAndDialogThemeAppTest`
+ * To run this test: `atest FlickerTestsIme2:ShowImeWhileDismissingThemedPopupDialogTest`
  */
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
index 7aa525f..c96c760 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
@@ -37,8 +37,8 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test IME window layer will be associated with the app task when going to the overview screen. To
- * run this test: `atest FlickerTests:OpenImeWindowToOverViewTest`
+ * Test IME window layer will be associated with the app task when going to the overview screen.
+ * To run this test: `atest FlickerTestsIme2:ShowImeWhileEnteringOverviewTest`
  */
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/Notification/AndroidTestTemplate.xml b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
index 9c6a17d3..04b312a 100644
--- a/tests/FlickerTests/Notification/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
@@ -80,6 +80,7 @@
                 value="trace_config.textproto"
         />
         <option name="instrumentation-arg" key="per_run" value="true"/>
+        <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
     </test>
     <!-- Needed for pulling the collected trace config on to the host -->
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt
index ffaeead..8c9ab9a 100644
--- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt
+++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt
@@ -40,7 +40,7 @@
  *
  * This test assumes the device doesn't have AOD enabled
  *
- * To run this test: `atest FlickerTests:OpenAppFromLockNotificationCold`
+ * To run this test: `atest FlickerTestsNotification:OpenAppFromLockscreenNotificationColdTest`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt
index 6e67e19..e595100a 100644
--- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt
+++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt
@@ -40,7 +40,7 @@
  *
  * This test assumes the device doesn't have AOD enabled
  *
- * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWarm`
+ * To run this test: `atest FlickerTestsNotification:OpenAppFromLockscreenNotificationWarmTest`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
index f1df8a6..fbe1d34 100644
--- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
+++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
@@ -40,7 +40,8 @@
  *
  * This test assumes the device doesn't have AOD enabled
  *
- * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWithLockOverlayApp`
+ * To run this test:
+ * `atest FlickerTestsNotification:OpenAppFromLockscreenNotificationWithOverlayAppTest`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.kt
index b6d09d0..c8ca644 100644
--- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.kt
+++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.kt
@@ -36,7 +36,7 @@
 /**
  * Test cold launching an app from a notification.
  *
- * To run this test: `atest FlickerTests:OpenAppFromNotificationCold`
+ * To run this test: `atest FlickerTestsNotification:OpenAppFromNotificationColdTest`
  */
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
index 1e607bf..c29e71c 100644
--- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
+++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
@@ -47,7 +47,7 @@
 /**
  * Test cold launching an app from a notification.
  *
- * To run this test: `atest FlickerTests:OpenAppFromNotificationWarm`
+ * To run this test: `atest FlickerTestsNotification:OpenAppFromNotificationWarmTest`
  */
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
index ecbed28..8acdabc 100644
--- a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
@@ -80,6 +80,7 @@
                 value="trace_config.textproto"
         />
         <option name="instrumentation-arg" key="per_run" value="true"/>
+        <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
     </test>
     <!-- Needed for pulling the collected trace config on to the host -->
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
index 1eacdfd..91ece21 100644
--- a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
@@ -80,6 +80,7 @@
                 value="trace_config.textproto"
         />
         <option name="instrumentation-arg" key="per_run" value="true"/>
+        <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
     </test>
     <!-- Needed for pulling the collected trace config on to the host -->
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 9198ae1..3e500d9 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -18,7 +18,10 @@
           package="com.android.server.wm.flicker.testapp">
 
     <uses-sdk android:minSdkVersion="29"
-              android:targetSdkVersion="29"/>
+              android:targetSdkVersion="35"/>
+
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+
     <application android:allowBackup="false"
                  android:supportsRtl="true">
         <uses-library android:name="androidx.window.extensions" android:required="false"/>
@@ -107,7 +110,7 @@
                   android:immersive="true"
                   android:resizeableActivity="true"
                   android:screenOrientation="portrait"
-                  android:theme="@android:style/Theme.NoTitleBar"
+                  android:theme="@style/OptOutEdgeToEdge.NoTitleBar"
                   android:configChanges="screenSize"
                   android:label="PortraitImmersiveActivity"
                   android:exported="true">
@@ -119,7 +122,7 @@
         <activity android:name=".LaunchTransparentActivity"
                   android:resizeableActivity="false"
                   android:screenOrientation="portrait"
-                  android:theme="@android:style/Theme"
+                  android:theme="@style/OptOutEdgeToEdge"
                   android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchTransparentActivity"
                   android:label="LaunchTransparentActivity"
                   android:exported="true">
@@ -273,7 +276,7 @@
                   android:exported="true"
                   android:label="MailActivity"
                   android:taskAffinity="com.android.server.wm.flicker.testapp.MailActivity"
-                  android:theme="@style/Theme.AppCompat.Light">
+                  android:theme="@style/OptOutEdgeToEdge.AppCompatTheme">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
@@ -282,7 +285,7 @@
         <activity android:name=".GameActivity"
                   android:taskAffinity="com.android.server.wm.flicker.testapp.GameActivity"
                   android:immersive="true"
-                  android:theme="@android:style/Theme.NoTitleBar"
+                  android:theme="@style/OptOutEdgeToEdge.NoTitleBar"
                   android:configChanges="screenSize"
                   android:label="GameActivity"
                   android:exported="true">
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
index 86c21906..917aec1 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
@@ -14,66 +14,71 @@
  See the License for the specific language governing permissions and
  limitations under the License.
 -->
-<LinearLayout
+<ScrollView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical"
     android:background="@android:color/holo_orange_light">
 
-    <Button
-        android:id="@+id/launch_secondary_activity_button"
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        android:onClick="launchSecondaryActivity"
-        android:tag="LEFT_TO_RIGHT"
-        android:text="Launch Secondary Activity" />
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
 
-    <Button
-        android:id="@+id/launch_secondary_activity_rtl_button"
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        android:onClick="launchSecondaryActivity"
-        android:tag="RIGHT_TO_LEFT"
-        android:text="Launch Secondary Activity in RTL" />
+        <Button
+            android:id="@+id/launch_secondary_activity_button"
+            android:layout_width="wrap_content"
+            android:layout_height="48dp"
+            android:onClick="launchSecondaryActivity"
+            android:tag="LEFT_TO_RIGHT"
+            android:text="Launch Secondary Activity" />
 
-    <Button
-        android:id="@+id/launch_secondary_activity_horizontally_button"
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        android:onClick="launchSecondaryActivity"
-        android:tag="BOTTOM_TO_TOP"
-        android:text="Launch Secondary Activity Horizontally" />
+        <Button
+            android:id="@+id/launch_secondary_activity_rtl_button"
+            android:layout_width="wrap_content"
+            android:layout_height="48dp"
+            android:onClick="launchSecondaryActivity"
+            android:tag="RIGHT_TO_LEFT"
+            android:text="Launch Secondary Activity in RTL" />
 
-    <Button
-        android:id="@+id/launch_placeholder_split_button"
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        android:onClick="launchPlaceholderSplit"
-        android:tag="LEFT_TO_RIGHT"
-        android:text="Launch Placeholder Split" />
+        <Button
+            android:id="@+id/launch_secondary_activity_horizontally_button"
+            android:layout_width="wrap_content"
+            android:layout_height="48dp"
+            android:onClick="launchSecondaryActivity"
+            android:tag="BOTTOM_TO_TOP"
+            android:text="Launch Secondary Activity Horizontally" />
 
-    <Button
-        android:id="@+id/launch_always_expand_activity_button"
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        android:onClick="launchAlwaysExpandActivity"
-        android:text="Launch Always Expand Activity" />
+        <Button
+            android:id="@+id/launch_placeholder_split_button"
+            android:layout_width="wrap_content"
+            android:layout_height="48dp"
+            android:onClick="launchPlaceholderSplit"
+            android:tag="LEFT_TO_RIGHT"
+            android:text="Launch Placeholder Split" />
 
-    <Button
-        android:id="@+id/launch_placeholder_split_rtl_button"
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        android:onClick="launchPlaceholderSplit"
-        android:tag="RIGHT_TO_LEFT"
-        android:text="Launch Placeholder Split in RTL" />
+        <Button
+            android:id="@+id/launch_always_expand_activity_button"
+            android:layout_width="wrap_content"
+            android:layout_height="48dp"
+            android:onClick="launchAlwaysExpandActivity"
+            android:text="Launch Always Expand Activity" />
 
-    <Button
-        android:id="@+id/launch_trampoline_button"
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        android:onClick="launchTrampolineActivity"
-        android:tag="LEFT_TO_RIGHT"
-        android:text="Launch Trampoline Activity" />
+        <Button
+            android:id="@+id/launch_placeholder_split_rtl_button"
+            android:layout_width="wrap_content"
+            android:layout_height="48dp"
+            android:onClick="launchPlaceholderSplit"
+            android:tag="RIGHT_TO_LEFT"
+            android:text="Launch Placeholder Split in RTL" />
 
-</LinearLayout>
+        <Button
+            android:id="@+id/launch_trampoline_button"
+            android:layout_width="wrap_content"
+            android:layout_height="48dp"
+            android:onClick="launchTrampolineActivity"
+            android:tag="LEFT_TO_RIGHT"
+            android:text="Launch Trampoline Activity" />
+
+    </LinearLayout>
+</ScrollView>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
index 9b742d9..47d1137 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
@@ -16,7 +16,19 @@
   -->
 
 <resources>
-    <style name="DefaultTheme" parent="@android:style/Theme.DeviceDefault">
+    <style name="OptOutEdgeToEdge" parent="@android:style/Theme.DeviceDefault">
+        <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
+    </style>
+
+    <style name="OptOutEdgeToEdge.NoTitleBar" parent="@android:style/Theme.NoTitleBar">
+        <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
+    </style>
+
+    <style name="OptOutEdgeToEdge.AppCompatTheme" parent="@style/Theme.AppCompat.Light">
+        <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
+    </style>
+
+    <style name="DefaultTheme" parent="@style/OptOutEdgeToEdge">
         <item name="android:windowBackground">@android:color/darker_gray</item>
     </style>
 
@@ -32,7 +44,7 @@
         <item name="android:windowLayoutInDisplayCutoutMode">never</item>
     </style>
 
-    <style name="DialogTheme" parent="@android:style/Theme.DeviceDefault">
+    <style name="DialogTheme" parent="@style/OptOutEdgeToEdge">
         <item name="android:windowAnimationStyle">@null</item>
         <item name="android:windowIsTranslucent">true</item>
         <item name="android:windowBackground">@null</item>
@@ -43,18 +55,18 @@
         <item name="android:windowSoftInputMode">stateUnchanged</item>
     </style>
 
-    <style name="TransparentTheme" parent="@android:style/Theme.DeviceDefault">
+    <style name="TransparentTheme" parent="@style/OptOutEdgeToEdge">
         <item name="android:windowIsTranslucent">true</item>
         <item name="android:windowBackground">@android:color/transparent</item>
         <item name="android:windowContentOverlay">@null</item>
         <item name="android:backgroundDimEnabled">false</item>
     </style>
 
-    <style name="no_starting_window" parent="@android:style/Theme.DeviceDefault">
+    <style name="no_starting_window" parent="@style/OptOutEdgeToEdge">
         <item name="android:windowDisablePreview">true</item>
     </style>
 
-    <style name="SplashscreenAppTheme" parent="@android:style/Theme.DeviceDefault">
+    <style name="SplashscreenAppTheme" parent="@style/OptOutEdgeToEdge">
         <!-- Splashscreen Attributes -->
         <item name="android:windowSplashScreenAnimatedIcon">@drawable/avd_anim</item>
         <!-- Here we want to match the duration of our AVD -->
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java
index c92b82b..a86ba5f 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java
@@ -125,7 +125,7 @@
                 .setContentTitle("BubbleChat")
                 .setContentIntent(PendingIntent.getActivity(mContext, 0,
                         new Intent(mContext, LaunchBubbleActivity.class),
-                        PendingIntent.FLAG_UPDATE_CURRENT))
+                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE))
                 .setStyle(new Notification.MessagingStyle(chatBot)
                         .setConversationTitle("BubbleChat")
                         .addMessage("BubbleChat",
@@ -140,7 +140,7 @@
         Intent target = new Intent(mContext, BubbleActivity.class);
         target.putExtra(EXTRA_BUBBLE_NOTIF_ID, info.id);
         PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, info.id, target,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
 
         return new Notification.BubbleMetadata.Builder()
                 .setIntent(bubbleIntent)
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java
index dea3444..37332c9 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java
@@ -17,6 +17,9 @@
 package com.android.server.wm.flicker.testapp;
 
 
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
 import android.app.Activity;
 import android.app.Person;
 import android.content.Context;
@@ -24,6 +27,7 @@
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager;
 import android.graphics.drawable.Icon;
+import android.os.Build;
 import android.os.Bundle;
 import android.view.View;
 
@@ -36,6 +40,13 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+                && checkSelfPermission(POST_NOTIFICATIONS) != PERMISSION_GRANTED) {
+            // POST_NOTIFICATIONS permission required for notification post sdk 33.
+            requestPermissions(new String[] { POST_NOTIFICATIONS }, 0);
+        }
+
         addInboxShortcut(getApplicationContext());
         mBubbleHelper = BubbleHelper.getInstance(this);
         setContentView(R.layout.activity_main);
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java
index a4dd575..d6427ab 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm.flicker.testapp;
 
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
 import android.app.Activity;
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -23,6 +26,7 @@
 import android.app.PendingIntent;
 import android.app.TaskStackBuilder;
 import android.content.Intent;
+import android.os.Build;
 import android.os.Bundle;
 import android.view.WindowManager;
 import android.widget.Button;
@@ -34,6 +38,13 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+                && checkSelfPermission(POST_NOTIFICATIONS) != PERMISSION_GRANTED) {
+            // POST_NOTIFICATIONS permission required for notification post sdk 33.
+            requestPermissions(new String[] { POST_NOTIFICATIONS }, 0);
+        }
+
         WindowManager.LayoutParams p = getWindow().getAttributes();
         p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
                 .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
index 1ab8ddb..27eb5a0 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
@@ -198,7 +198,7 @@
         filter.addAction(ACTION_SET_REQUESTED_ORIENTATION);
         filter.addAction(ACTION_ENTER_PIP);
         filter.addAction(ACTION_ASPECT_RATIO);
-        registerReceiver(mBroadcastReceiver, filter);
+        registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED);
 
         handleIntentExtra(getIntent());
     }
@@ -222,8 +222,8 @@
 
     private RemoteAction buildRemoteAction(Icon icon, String label, String action) {
         final Intent intent = new Intent(action);
-        final PendingIntent pendingIntent =
-                PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+        final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent,
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         return new RemoteAction(icon, label, label, pendingIntent);
     }
 
diff --git a/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
index 3a2a3be..ae32bda 100644
--- a/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
+++ b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
@@ -16,6 +16,8 @@
 
 package android.hardware.input
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.content.ContextWrapper
 import android.graphics.drawable.Drawable
 import android.platform.test.annotations.Presubmit
@@ -54,16 +56,16 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
     fun testKeyboardLayoutDrawable_hasCorrectDimensions() {
-        setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
         val drawable = createDrawable()!!
         assertEquals(WIDTH, drawable.intrinsicWidth)
         assertEquals(HEIGHT, drawable.intrinsicHeight)
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
     fun testKeyboardLayoutDrawable_isNull_ifFlagOff() {
-        setFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
         assertNull(createDrawable())
     }
 }
\ No newline at end of file
diff --git a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
index e2b0c36..bcd56ad 100644
--- a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
+++ b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
@@ -21,6 +21,7 @@
 import android.os.Handler
 import android.os.HandlerExecutor
 import android.os.test.TestLooper
+import android.platform.test.annotations.EnableFlags
 import android.platform.test.annotations.Presubmit
 import android.platform.test.flag.junit.SetFlagsRule
 import android.view.KeyEvent
@@ -50,6 +51,10 @@
  */
 @Presubmit
 @RunWith(MockitoJUnitRunner::class)
+@EnableFlags(
+    com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
+    com.android.input.flags.Flags.FLAG_ENABLE_INPUT_FILTER_RUST_IMPL,
+)
 class StickyModifierStateListenerTest {
 
     @get:Rule
@@ -67,10 +72,6 @@
 
     @Before
     fun setUp() {
-        // Enable Sticky keys feature
-        rule.enableFlags(com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG)
-        rule.enableFlags(com.android.input.flags.Flags.FLAG_ENABLE_INPUT_FILTER_RUST_IMPL)
-
         context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
         inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock)
         inputManager = InputManager(context)
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 0fc9d6f..3c72498 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -28,14 +28,11 @@
 import android.os.SystemClock
 import android.os.test.TestLooper
 import android.platform.test.annotations.Presubmit
-import android.platform.test.annotations.RequiresFlagsDisabled
 import android.platform.test.flag.junit.DeviceFlagsValueProvider
 import android.provider.Settings
 import android.view.View.OnKeyListener
-import android.view.Display
 import android.view.InputDevice
 import android.view.KeyEvent
-import android.view.PointerIcon
 import android.view.SurfaceHolder
 import android.view.SurfaceView
 import android.test.mock.MockContentResolver
@@ -44,7 +41,6 @@
 import com.google.common.truth.Truth.assertThat
 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
 import org.junit.After
-import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Rule
@@ -53,22 +49,16 @@
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyFloat
 import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito.`when`
-import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.doAnswer
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.Mockito.verifyZeroInteractions
 import org.mockito.junit.MockitoJUnit
 import org.mockito.stubbing.OngoingStubbing
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
 
 /**
  * Tests for {@link InputManagerService}.
@@ -179,203 +169,6 @@
         localService.setDisplayViewports(viewports)
         verify(native).setDisplayViewports(any(Array<DisplayViewport>::class.java))
         verify(native).setPointerDisplayId(displayId)
-
-        val x = 42f
-        val y = 314f
-        service.onPointerDisplayIdChanged(displayId, x, y)
-        testLooper.dispatchNext()
-        verify(wmCallbacks).notifyPointerDisplayIdChanged(displayId, x, y)
-    }
-
-    @RequiresFlagsDisabled(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
-    @Test
-    fun testSetVirtualMousePointerDisplayId() {
-        // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
-        // until the native callback happens.
-        var countDownLatch = CountDownLatch(1)
-        val overrideDisplayId = 123
-        Thread {
-            assertTrue("Setting virtual pointer display should succeed",
-                localService.setVirtualMousePointerDisplayId(overrideDisplayId))
-            countDownLatch.countDown()
-        }.start()
-        assertFalse("Setting virtual pointer display should block",
-            countDownLatch.await(100, TimeUnit.MILLISECONDS))
-
-        val x = 42f
-        val y = 314f
-        service.onPointerDisplayIdChanged(overrideDisplayId, x, y)
-        testLooper.dispatchNext()
-        verify(wmCallbacks).notifyPointerDisplayIdChanged(overrideDisplayId, x, y)
-        assertTrue("Native callback unblocks calling thread",
-            countDownLatch.await(100, TimeUnit.MILLISECONDS))
-        verify(native).setPointerDisplayId(overrideDisplayId)
-
-        // Ensure that setting the same override again succeeds immediately.
-        assertTrue("Setting the same virtual mouse pointer displayId again should succeed",
-            localService.setVirtualMousePointerDisplayId(overrideDisplayId))
-
-        // Ensure that we did not query WM for the pointerDisplayId when setting the override
-        verify(wmCallbacks, never()).pointerDisplayId
-
-        // Unset the virtual mouse pointer displayId, and ensure that we query WM for the new
-        // pointer displayId and the calling thread is blocked until the native callback happens.
-        countDownLatch = CountDownLatch(1)
-        val pointerDisplayId = 42
-        `when`(wmCallbacks.pointerDisplayId).thenReturn(pointerDisplayId)
-        Thread {
-            assertTrue("Unsetting virtual mouse pointer displayId should succeed",
-                localService.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY))
-            countDownLatch.countDown()
-        }.start()
-        assertFalse("Unsetting virtual mouse pointer displayId should block",
-            countDownLatch.await(100, TimeUnit.MILLISECONDS))
-
-        service.onPointerDisplayIdChanged(pointerDisplayId, x, y)
-        testLooper.dispatchNext()
-        verify(wmCallbacks).notifyPointerDisplayIdChanged(pointerDisplayId, x, y)
-        assertTrue("Native callback unblocks calling thread",
-            countDownLatch.await(100, TimeUnit.MILLISECONDS))
-        verify(native).setPointerDisplayId(pointerDisplayId)
-    }
-
-    @RequiresFlagsDisabled(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
-    @Test
-    fun testSetVirtualMousePointerDisplayId_unsuccessfulUpdate() {
-        // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
-        // until the native callback happens.
-        val countDownLatch = CountDownLatch(1)
-        val overrideDisplayId = 123
-        Thread {
-            assertFalse("Setting virtual pointer display should be unsuccessful",
-                localService.setVirtualMousePointerDisplayId(overrideDisplayId))
-            countDownLatch.countDown()
-        }.start()
-        assertFalse("Setting virtual pointer display should block",
-            countDownLatch.await(100, TimeUnit.MILLISECONDS))
-
-        val x = 42f
-        val y = 314f
-        // Assume the native callback updates the pointerDisplayId to the incorrect value.
-        service.onPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y)
-        testLooper.dispatchNext()
-        verify(wmCallbacks).notifyPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y)
-        assertTrue("Native callback unblocks calling thread",
-            countDownLatch.await(100, TimeUnit.MILLISECONDS))
-        verify(native).setPointerDisplayId(overrideDisplayId)
-    }
-
-    @RequiresFlagsDisabled(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
-    @Test
-    fun testSetVirtualMousePointerDisplayId_competingRequests() {
-        val firstRequestSyncLatch = CountDownLatch(1)
-        doAnswer {
-            firstRequestSyncLatch.countDown()
-        }.`when`(native).setPointerDisplayId(anyInt())
-
-        val firstRequestLatch = CountDownLatch(1)
-        val firstOverride = 123
-        Thread {
-            assertFalse("Setting virtual pointer display from thread 1 should be unsuccessful",
-                localService.setVirtualMousePointerDisplayId(firstOverride))
-            firstRequestLatch.countDown()
-        }.start()
-        assertFalse("Setting virtual pointer display should block",
-            firstRequestLatch.await(100, TimeUnit.MILLISECONDS))
-
-        assertTrue("Wait for first thread's request should succeed",
-            firstRequestSyncLatch.await(100, TimeUnit.MILLISECONDS))
-
-        val secondRequestLatch = CountDownLatch(1)
-        val secondOverride = 42
-        Thread {
-            assertTrue("Setting virtual mouse pointer from thread 2 should be successful",
-                localService.setVirtualMousePointerDisplayId(secondOverride))
-            secondRequestLatch.countDown()
-        }.start()
-        assertFalse("Setting virtual mouse pointer should block",
-            secondRequestLatch.await(100, TimeUnit.MILLISECONDS))
-
-        val x = 42f
-        val y = 314f
-        // Assume the native callback updates directly to the second request.
-        service.onPointerDisplayIdChanged(secondOverride, x, y)
-        testLooper.dispatchNext()
-        verify(wmCallbacks).notifyPointerDisplayIdChanged(secondOverride, x, y)
-        assertTrue("Native callback unblocks first thread",
-            firstRequestLatch.await(100, TimeUnit.MILLISECONDS))
-        assertTrue("Native callback unblocks second thread",
-            secondRequestLatch.await(100, TimeUnit.MILLISECONDS))
-        verify(native, times(2)).setPointerDisplayId(anyInt())
-    }
-
-    @RequiresFlagsDisabled(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
-    @Test
-    fun onDisplayRemoved_resetAllAdditionalInputProperties() {
-        setVirtualMousePointerDisplayIdAndVerify(10)
-
-        localService.setPointerIconVisible(false, 10)
-        verify(native).setPointerIconVisibility(10, false)
-        verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
-        localService.setMousePointerAccelerationEnabled(false, 10)
-        verify(native).setMousePointerAccelerationEnabled(10, false)
-
-        service.onDisplayRemoved(10)
-        verify(native).setPointerIconVisibility(10, true)
-        verify(native).displayRemoved(eq(10))
-        verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED))
-        verify(native).setMousePointerAccelerationEnabled(10, true)
-        verifyNoMoreInteractions(native)
-
-        // This call should not block because the virtual mouse pointer override was never removed.
-        localService.setVirtualMousePointerDisplayId(10)
-
-        verify(native).setPointerDisplayId(eq(10))
-        verifyNoMoreInteractions(native)
-    }
-
-    @RequiresFlagsDisabled(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
-    @Test
-    fun updateAdditionalInputPropertiesForOverrideDisplay() {
-        setVirtualMousePointerDisplayIdAndVerify(10)
-
-        localService.setPointerIconVisible(false, 10)
-        verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
-        verify(native).setPointerIconVisibility(10, false)
-        localService.setMousePointerAccelerationEnabled(false, 10)
-        verify(native).setMousePointerAccelerationEnabled(10, false)
-
-        localService.setPointerIconVisible(true, 10)
-        verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED))
-        verify(native).setPointerIconVisibility(10, true)
-        localService.setMousePointerAccelerationEnabled(true, 10)
-        verify(native).setMousePointerAccelerationEnabled(10, true)
-
-        localService.setPointerIconVisible(false, 20)
-        verify(native).setPointerIconVisibility(20, false)
-        localService.setMousePointerAccelerationEnabled(false, 20)
-        verify(native).setMousePointerAccelerationEnabled(20, false)
-        verifyNoMoreInteractions(native)
-
-        clearInvocations(native)
-        setVirtualMousePointerDisplayIdAndVerify(20)
-
-        verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
-    }
-
-    @RequiresFlagsDisabled(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
-    @Test
-    fun setAdditionalInputPropertiesBeforeOverride() {
-        localService.setPointerIconVisible(false, 10)
-        localService.setMousePointerAccelerationEnabled(false, 10)
-
-        verify(native).setPointerIconVisibility(10, false)
-        verify(native).setMousePointerAccelerationEnabled(10, false)
-        verifyNoMoreInteractions(native)
-
-        setVirtualMousePointerDisplayIdAndVerify(10)
-
-        verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
     }
 
     @Test
@@ -412,20 +205,6 @@
         verify(native, times(2)).changeKeyboardLayoutAssociation()
     }
 
-    private fun setVirtualMousePointerDisplayIdAndVerify(overrideDisplayId: Int) {
-        val thread = Thread { localService.setVirtualMousePointerDisplayId(overrideDisplayId) }
-        thread.start()
-
-        // Allow some time for the set override call to park while waiting for the native callback.
-        Thread.sleep(100 /*millis*/)
-        verify(native).setPointerDisplayId(overrideDisplayId)
-
-        service.onPointerDisplayIdChanged(overrideDisplayId, 0f, 0f)
-        testLooper.dispatchNext()
-        verify(wmCallbacks).notifyPointerDisplayIdChanged(overrideDisplayId, 0f, 0f)
-        thread.join(100 /*millis*/)
-    }
-
     private fun createVirtualDisplays(count: Int): List<VirtualDisplay> {
         val displayManager: DisplayManager = context.getSystemService(
                 DisplayManager::class.java
@@ -532,9 +311,8 @@
         verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent)
     }
 
-    // TODO(b/324075859): Rename this method to addUniqueIdAssociationByPort_verifyAssociations
     @Test
-    fun addUniqueIdAssociation_verifyAssociations() {
+    fun addUniqueIdAssociationByPort_verifyAssociations() {
         // Overall goal is to have 2 displays and verify that events from the InputDevice are
         // sent only to the view that is on the associated display.
         // So, associate the InputDevice with display 1, then send and verify KeyEvents.
@@ -555,7 +333,7 @@
         val inputDevice = createInputDevice()
 
         // Associate input device with display
-        service.addUniqueIdAssociation(
+        service.addUniqueIdAssociationByPort(
                 inputDevice.name,
                 virtualDisplays[0].display.displayId.toString()
         )
@@ -579,10 +357,10 @@
         verify(mockOnKeyListener, never()).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, downEvent)
 
         // Remove association
-        service.removeUniqueIdAssociation(inputDevice.name)
+        service.removeUniqueIdAssociationByPort(inputDevice.name)
 
         // Associate with Display 2
-        service.addUniqueIdAssociation(
+        service.addUniqueIdAssociationByPort(
                 inputDevice.name,
                 virtualDisplays[1].display.displayId.toString()
         )
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java b/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
similarity index 99%
rename from tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java
rename to tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
index 001a09a..be9fb1b 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
@@ -34,7 +34,7 @@
 import perfetto.protos.ProtologCommon;
 import perfetto.protos.ProtologConfig;
 
-public class PerfettoDataSourceTest {
+public class ProtologDataSourceTest {
     @Before
     public void before() {
         assumeTrue(android.tracing.Flags.perfettoProtologTracing());
diff --git a/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java b/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java
index 3ab8d37..6bcfebc 100644
--- a/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java
+++ b/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java
@@ -176,25 +176,19 @@
         },
         new Test("Disable Alerts") {
             public void run() {
-                StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
-                info.setNotificationPeekingDisabled(true);
-                mStatusBarManager.requestDisabledComponent(info, "test");
+                mStatusBarManager.disable(StatusBarManager.DISABLE_NOTIFICATION_ALERTS);
             }
         },
         new Test("Disable Ticker") {
             public void run() {
-                StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
-                info.setNotificationTickerDisabled(true);
-                mStatusBarManager.requestDisabledComponent(info, "test");
+                mStatusBarManager.disable(StatusBarManager.DISABLE_NOTIFICATION_TICKER);
             }
         },
         new Test("Disable Expand in 3 sec.") {
             public void run() {
                 mHandler.postDelayed(new Runnable() {
                         public void run() {
-                            StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
-                            info.setStatusBarExpansionDisabled(true);
-                            mStatusBarManager.requestDisabledComponent(info, "test");
+                            mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND);
                         }
                     }, 3000);
             }
@@ -203,9 +197,7 @@
             public void run() {
                 mHandler.postDelayed(new Runnable() {
                         public void run() {
-                            StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
-                            info.setNotificationIconsDisabled(true);
-                            mStatusBarManager.requestDisabledComponent(info, "test");
+                            mStatusBarManager.disable(StatusBarManager.DISABLE_NOTIFICATION_ICONS);
                         }
                     }, 3000);
             }
@@ -214,73 +206,56 @@
             public void run() {
                 mHandler.postDelayed(new Runnable() {
                         public void run() {
-                            StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
-                            info.setStatusBarExpansionDisabled(true);
-                            info.setNotificationIconsDisabled(true);
-                            mStatusBarManager.requestDisabledComponent(info, "test");
+                            mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND
+                                    | StatusBarManager.DISABLE_NOTIFICATION_ICONS);
                         }
                     }, 3000);
             }
         },
         new Test("Disable Home (StatusBarManager)") {
             public void run() {
-                StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
-                info.setNavigationHomeDisabled(true);
-                mStatusBarManager.requestDisabledComponent(info, "test");
+                mStatusBarManager.disable(StatusBarManager.DISABLE_HOME);
             }
         },
         new Test("Disable Back (StatusBarManager)") {
             public void run() {
-                StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
-                info.setBackDisabled(true);
-                mStatusBarManager.requestDisabledComponent(info, "test");
+                mStatusBarManager.disable(StatusBarManager.DISABLE_BACK);
             }
         },
         new Test("Disable Recent (StatusBarManager)") {
             public void run() {
-                StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
-                info.setRecentsDisabled(true);
-                mStatusBarManager.requestDisabledComponent(info, "test");
+                mStatusBarManager.disable(StatusBarManager.DISABLE_RECENT);
             }
         },
         new Test("Disable Clock") {
             public void run() {
-                StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
-                info.setClockDisabled(true);
-                mStatusBarManager.requestDisabledComponent(info, "test");
+                mStatusBarManager.disable(StatusBarManager.DISABLE_CLOCK);
             }
         },
         new Test("Disable System Info") {
             public void run() {
-                StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
-                info.setSystemIconsDisabled(true);
-                mStatusBarManager.requestDisabledComponent(info, "test");
+                mStatusBarManager.disable(StatusBarManager.DISABLE_SYSTEM_INFO);
             }
         },
         new Test("Disable everything in 3 sec") {
             public void run() {
                 mHandler.postDelayed(new Runnable() {
                         public void run() {
-                            StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
-                            info.setDisableAll();
-                            mStatusBarManager.requestDisabledComponent(info, "test");
+                            mStatusBarManager.disable(~StatusBarManager.DISABLE_NONE);
                         }
                     }, 3000);
             }
         },
         new Test("Enable everything") {
             public void run() {
-                StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
-                mStatusBarManager.requestDisabledComponent(info, "test");
+                mStatusBarManager.disable(StatusBarManager.DISABLE_NONE);
             }
         },
         new Test("Enable everything in 3 sec.") {
             public void run() {
                 mHandler.postDelayed(new Runnable() {
                         public void run() {
-                            StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo();
-                            info.setEnableAll();
-                            mStatusBarManager.requestDisabledComponent(info, "test");
+                            mStatusBarManager.disable(0);
                         }
                     }, 3000);
             }
diff --git a/tests/UsbManagerTests/Android.bp b/tests/UsbManagerTests/Android.bp
index f0bea3f..2909e66 100644
--- a/tests/UsbManagerTests/Android.bp
+++ b/tests/UsbManagerTests/Android.bp
@@ -43,6 +43,9 @@
         "libmultiplejvmtiagentsinterferenceagent",
         "libstaticjvmtiagent",
     ],
+    libs: [
+        "android.test.mock",
+    ],
     certificate: "platform",
     platform_apis: true,
     test_suites: ["device-tests"],
diff --git a/tests/UsbManagerTests/src/android/hardware/usb/DeviceFilterTest.java b/tests/UsbManagerTests/src/android/hardware/usb/DeviceFilterTest.java
new file mode 100644
index 0000000..d6f3148
--- /dev/null
+++ b/tests/UsbManagerTests/src/android/hardware/usb/DeviceFilterTest.java
@@ -0,0 +1,248 @@
+/*
+ * 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.hardware.usb;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.usb.flags.Flags;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.util.XmlUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.StringReader;
+
+/**
+ * Unit tests for {@link android.hardware.usb.DeviceFilter}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class DeviceFilterTest {
+
+    private static final int VID = 10;
+    private static final int PID = 11;
+    private static final int CLASS = 12;
+    private static final int SUBCLASS = 13;
+    private static final int PROTOCOL = 14;
+    private static final String MANUFACTURER = "Google";
+    private static final String PRODUCT = "Test";
+    private static final String SERIAL_NO = "4AL23";
+    private static final String INTERFACE_NAME = "MTP";
+
+    private MockitoSession mStaticMockSession;
+
+    @Before
+    public void setUp() throws Exception {
+        mStaticMockSession = ExtendedMockito.mockitoSession()
+                .mockStatic(Flags.class)
+                .strictness(Strictness.WARN)
+                .startMocking();
+
+        when(Flags.enableInterfaceNameDeviceFilter()).thenReturn(true);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mStaticMockSession.finishMocking();
+    }
+
+    @Test
+    public void testConstructorFromValues_interfaceNameIsInitialized() {
+        DeviceFilter deviceFilter = new DeviceFilter(
+                VID, PID, CLASS, SUBCLASS, PROTOCOL, MANUFACTURER,
+                PRODUCT, SERIAL_NO, INTERFACE_NAME
+        );
+
+        verifyDeviceFilterConfigurationExceptInterfaceName(deviceFilter);
+        assertThat(deviceFilter.mInterfaceName).isEqualTo(INTERFACE_NAME);
+    }
+
+    @Test
+    public void testConstructorFromUsbDevice_interfaceNameIsNull() {
+        UsbDevice usbDevice = Mockito.mock(UsbDevice.class);
+        when(usbDevice.getVendorId()).thenReturn(VID);
+        when(usbDevice.getProductId()).thenReturn(PID);
+        when(usbDevice.getDeviceClass()).thenReturn(CLASS);
+        when(usbDevice.getDeviceSubclass()).thenReturn(SUBCLASS);
+        when(usbDevice.getDeviceProtocol()).thenReturn(PROTOCOL);
+        when(usbDevice.getManufacturerName()).thenReturn(MANUFACTURER);
+        when(usbDevice.getProductName()).thenReturn(PRODUCT);
+        when(usbDevice.getSerialNumber()).thenReturn(SERIAL_NO);
+
+        DeviceFilter deviceFilter = new DeviceFilter(usbDevice);
+
+        verifyDeviceFilterConfigurationExceptInterfaceName(deviceFilter);
+        assertThat(deviceFilter.mInterfaceName).isEqualTo(null);
+    }
+
+    @Test
+    public void testConstructorFromDeviceFilter_interfaceNameIsInitialized() {
+        DeviceFilter originalDeviceFilter = new DeviceFilter(
+                VID, PID, CLASS, SUBCLASS, PROTOCOL, MANUFACTURER,
+                PRODUCT, SERIAL_NO, INTERFACE_NAME
+        );
+
+        DeviceFilter deviceFilter = new DeviceFilter(originalDeviceFilter);
+
+        verifyDeviceFilterConfigurationExceptInterfaceName(deviceFilter);
+        assertThat(deviceFilter.mInterfaceName).isEqualTo(INTERFACE_NAME);
+    }
+
+
+    @Test
+    public void testReadFromXml_interfaceNamePresent_propertyIsInitialized() throws Exception {
+        DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device interface-name=\"MTP\"/>");
+
+        assertThat(deviceFilter.mInterfaceName).isEqualTo("MTP");
+    }
+
+    @Test
+    public void testReadFromXml_interfaceNameAbsent_propertyIsNull() throws Exception {
+        DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device vendor-id=\"1\" />");
+
+        assertThat(deviceFilter.mInterfaceName).isEqualTo(null);
+    }
+
+    @Test
+    public void testWrite_withInterfaceName() throws Exception {
+        DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device interface-name=\"MTP\"/>");
+        XmlSerializer serializer = Mockito.mock(XmlSerializer.class);
+
+        deviceFilter.write(serializer);
+
+        verify(serializer).attribute(null, "interface-name", "MTP");
+    }
+
+    @Test
+    public void testWrite_withoutInterfaceName() throws Exception {
+        DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device vendor-id=\"1\" />");
+        XmlSerializer serializer = Mockito.mock(XmlSerializer.class);
+
+        deviceFilter.write(serializer);
+
+        verify(serializer, times(0)).attribute(eq(null), eq("interface-name"), any());
+    }
+
+    @Test
+    public void testToString() {
+        DeviceFilter deviceFilter = new DeviceFilter(
+                VID, PID, CLASS, SUBCLASS, PROTOCOL, MANUFACTURER,
+                PRODUCT, SERIAL_NO, INTERFACE_NAME
+        );
+
+        assertThat(deviceFilter.toString()).isEqualTo(
+                "DeviceFilter[mVendorId=10,mProductId=11,mClass=12,mSubclass=13,mProtocol=14,"
+                + "mManufacturerName=Google,mProductName=Test,mSerialNumber=4AL23,"
+                + "mInterfaceName=MTP]");
+    }
+
+    @Test
+    public void testMatch_interfaceNameMatches_returnTrue() throws Exception {
+        DeviceFilter deviceFilter = getDeviceFilterFromXml(
+                "<usb-device class=\"255\" subclass=\"255\" protocol=\"0\" "
+                + "interface-name=\"MTP\"/>");
+        UsbDevice usbDevice = Mockito.mock(UsbDevice.class);
+        when(usbDevice.getInterfaceCount()).thenReturn(1);
+        when(usbDevice.getInterface(0)).thenReturn(new UsbInterface(
+            /* id= */ 0,
+            /* alternateSetting= */ 0,
+            /* name= */ "MTP",
+            /* class= */ 255,
+            /* subClass= */ 255,
+            /* protocol= */ 0));
+
+        assertTrue(deviceFilter.matches(usbDevice));
+    }
+
+    @Test
+    public void testMatch_interfaceNameMismatch_returnFalse() throws Exception {
+        DeviceFilter deviceFilter = getDeviceFilterFromXml(
+                "<usb-device class=\"255\" subclass=\"255\" protocol=\"0\" "
+                + "interface-name=\"MTP\"/>");
+        UsbDevice usbDevice = Mockito.mock(UsbDevice.class);
+        when(usbDevice.getInterfaceCount()).thenReturn(1);
+        when(usbDevice.getInterface(0)).thenReturn(new UsbInterface(
+            /* id= */ 0,
+            /* alternateSetting= */ 0,
+            /* name= */ "UVC",
+            /* class= */ 255,
+            /* subClass= */ 255,
+            /* protocol= */ 0));
+
+        assertFalse(deviceFilter.matches(usbDevice));
+    }
+
+    @Test
+    public void testMatch_interfaceNameMismatchFlagDisabled_returnTrue() throws Exception {
+        when(Flags.enableInterfaceNameDeviceFilter()).thenReturn(false);
+        DeviceFilter deviceFilter = getDeviceFilterFromXml(
+                "<usb-device class=\"255\" subclass=\"255\" protocol=\"0\" "
+                + "interface-name=\"MTP\"/>");
+        UsbDevice usbDevice = Mockito.mock(UsbDevice.class);
+        when(usbDevice.getInterfaceCount()).thenReturn(1);
+        when(usbDevice.getInterface(0)).thenReturn(new UsbInterface(
+            /* id= */ 0,
+            /* alternateSetting= */ 0,
+            /* name= */ "UVC",
+            /* class= */ 255,
+            /* subClass= */ 255,
+            /* protocol= */ 0));
+
+        assertTrue(deviceFilter.matches(usbDevice));
+    }
+
+    private void verifyDeviceFilterConfigurationExceptInterfaceName(DeviceFilter deviceFilter) {
+        assertThat(deviceFilter.mVendorId).isEqualTo(VID);
+        assertThat(deviceFilter.mProductId).isEqualTo(PID);
+        assertThat(deviceFilter.mClass).isEqualTo(CLASS);
+        assertThat(deviceFilter.mSubclass).isEqualTo(SUBCLASS);
+        assertThat(deviceFilter.mProtocol).isEqualTo(PROTOCOL);
+        assertThat(deviceFilter.mManufacturerName).isEqualTo(MANUFACTURER);
+        assertThat(deviceFilter.mProductName).isEqualTo(PRODUCT);
+        assertThat(deviceFilter.mSerialNumber).isEqualTo(SERIAL_NO);
+    }
+
+    private DeviceFilter getDeviceFilterFromXml(String xml) throws Exception {
+        XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+        XmlPullParser parser = factory.newPullParser();
+        parser.setInput(new StringReader(xml));
+        XmlUtils.nextElement(parser);
+
+        return DeviceFilter.read(parser);
+    }
+
+}
diff --git a/tests/UsbManagerTests/src/com/android/server/usbtest/UsbProfileGroupSettingsManagerTest.java b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbProfileGroupSettingsManagerTest.java
index 4780d8a..87b26a6 100644
--- a/tests/UsbManagerTests/src/com/android/server/usbtest/UsbProfileGroupSettingsManagerTest.java
+++ b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbProfileGroupSettingsManagerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.usbtest;
 
+import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
+
 import static com.android.server.usb.UsbProfileGroupSettingsManager.PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -32,16 +34,20 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManager.Property;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.hardware.usb.UsbDevice;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
 
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.usb.UsbHandlerManager;
 import com.android.server.usb.UsbProfileGroupSettingsManager;
 import com.android.server.usb.UsbSettingsManager;
@@ -69,6 +75,7 @@
 public class UsbProfileGroupSettingsManagerTest {
 
     private static final String TEST_PACKAGE_NAME = "testPkg";
+
     @Mock
     private Context mContext;
     @Mock
@@ -85,43 +92,78 @@
     private UserManager mUserManager;
     @Mock
     private UsbUserSettingsManager mUsbUserSettingsManager;
-    @Mock private Property mProperty;
-    private ActivityManager.RunningAppProcessInfo mRunningAppProcessInfo;
-    private PackageInfo mPackageInfo;
-    private UsbProfileGroupSettingsManager mUsbProfileGroupSettingsManager;
+    @Mock
+    private Property mRestrictUsbOverlayActivitiesProperty;
+    @Mock
+    private UsbDevice mUsbDevice;
+
+    private MockContentResolver mContentResolver;
     private MockitoSession mStaticMockSession;
 
+    private UsbProfileGroupSettingsManager mUsbProfileGroupSettingsManager;
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-
-        mRunningAppProcessInfo = new ActivityManager.RunningAppProcessInfo();
-        mRunningAppProcessInfo.pkgList = new String[]{TEST_PACKAGE_NAME};
-        mPackageInfo = new PackageInfo();
-        mPackageInfo.packageName = TEST_PACKAGE_NAME;
-        mPackageInfo.applicationInfo = Mockito.mock(ApplicationInfo.class);
-
-        when(mContext.getPackageManager()).thenReturn(mPackageManager);
-        when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager);
-        when(mContext.getResources()).thenReturn(Mockito.mock(Resources.class));
-        when(mContext.createPackageContextAsUser(anyString(), anyInt(), any(UserHandle.class)))
-                .thenReturn(mContext);
-        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
-
-        mUsbProfileGroupSettingsManager = new UsbProfileGroupSettingsManager(mContext, mUserHandle,
-                mUsbSettingsManager, mUsbHandlerManager);
-
         mStaticMockSession = ExtendedMockito.mockitoSession()
                 .mockStatic(Flags.class)
                 .strictness(Strictness.WARN)
                 .startMocking();
 
-        when(mPackageManager.getPackageInfo(TEST_PACKAGE_NAME, 0)).thenReturn(mPackageInfo);
-        when(mPackageManager.getProperty(eq(PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES),
-                eq(TEST_PACKAGE_NAME))).thenReturn(mProperty);
+        when(mUsbSettingsManager.getSettingsForUser(anyInt())).thenReturn(mUsbUserSettingsManager);
         when(mUserManager.getEnabledProfiles(anyInt()))
                 .thenReturn(List.of(Mockito.mock(UserInfo.class)));
-        when(mUsbSettingsManager.getSettingsForUser(anyInt())).thenReturn(mUsbUserSettingsManager);
+
+        mContentResolver = new MockContentResolver();
+        mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+
+        when(mContext.getContentResolver()).thenReturn(mContentResolver);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mContext.getResources()).thenReturn(Mockito.mock(Resources.class));
+        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+        when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager);
+        when(mContext.createPackageContextAsUser(anyString(), anyInt(), any(UserHandle.class)))
+                .thenReturn(mContext);
+
+        mUsbProfileGroupSettingsManager = new UsbProfileGroupSettingsManager(
+                mContext, mUserHandle, mUsbSettingsManager, mUsbHandlerManager);
+
+        setupDefaultConfiguration();
+    }
+
+    /**
+     * Setups the following configuration
+     *
+     * <ul>
+     * <li>Flag is enabled
+     * <li>Device setup has completed
+     * <li>There is a foreground activity with MANAGE_USB permission
+     * <li>The foreground activity has PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES enabled
+     * </ul>
+     */
+    private void setupDefaultConfiguration() throws NameNotFoundException {
+        when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true);
+
+        Settings.Secure.putInt(mContentResolver, USER_SETUP_COMPLETE, 1);
+
+        ActivityManager.RunningAppProcessInfo mRunningAppProcessInfo =
+                new ActivityManager.RunningAppProcessInfo();
+        mRunningAppProcessInfo.pkgList = new String[] { TEST_PACKAGE_NAME };
+        when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo));
+
+        PackageInfo mPackageInfo = new PackageInfo();
+        mPackageInfo.packageName = TEST_PACKAGE_NAME;
+        mPackageInfo.applicationInfo = Mockito.mock(ApplicationInfo.class);
+        when(mPackageManager.getPackageInfo(TEST_PACKAGE_NAME, 0)).thenReturn(mPackageInfo);
+        when(mPackageManager.getPackagesHoldingPermissions(
+                new String[] { android.Manifest.permission.MANAGE_USB },
+                PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(List.of(mPackageInfo));
+
+        when(mRestrictUsbOverlayActivitiesProperty.getBoolean()).thenReturn(true);
+        when(mPackageManager.getProperty(
+                eq(PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES), eq(TEST_PACKAGE_NAME)))
+                .thenReturn(mRestrictUsbOverlayActivitiesProperty);
     }
 
     @After
@@ -130,66 +172,59 @@
     }
 
     @Test
-    public void testDeviceAttached_flagTrueWithoutForegroundActivity_resolveActivityCalled() {
-        when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true);
+    public void testDeviceAttached_foregroundActivityWithManifestField_resolveActivityNotCalled() {
+        mUsbProfileGroupSettingsManager.deviceAttached(mUsbDevice);
+
+        verify(mUsbUserSettingsManager, times(0)).queryIntentActivities(any(Intent.class));
+    }
+
+    @Test
+    public void testDeviceAttached_noForegroundActivity_resolveActivityCalled() {
         when(mActivityManager.getRunningAppProcesses()).thenReturn(new ArrayList<>());
-        when(mPackageManager.getPackagesHoldingPermissions(
-                new String[]{android.Manifest.permission.MANAGE_USB},
-                PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(List.of(mPackageInfo));
-        UsbDevice device = Mockito.mock(UsbDevice.class);
-        mUsbProfileGroupSettingsManager.deviceAttached(device);
+
+        mUsbProfileGroupSettingsManager.deviceAttached(mUsbDevice);
+
         verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class));
     }
 
     @Test
     public void testDeviceAttached_noForegroundActivityWithUsbPermission_resolveActivityCalled() {
-        when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true);
-        when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo));
         when(mPackageManager.getPackagesHoldingPermissions(
-                new String[]{android.Manifest.permission.MANAGE_USB},
-                PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(new ArrayList<>());
-        UsbDevice device = Mockito.mock(UsbDevice.class);
-        mUsbProfileGroupSettingsManager.deviceAttached(device);
+                new String[] { android.Manifest.permission.MANAGE_USB },
+                PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(new ArrayList<>());
+
+        mUsbProfileGroupSettingsManager.deviceAttached(mUsbDevice);
+
         verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class));
     }
 
     @Test
-    public void testDeviceAttached_foregroundActivityWithManifestField_resolveActivityNotCalled() {
-        when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true);
-        when(mProperty.getBoolean()).thenReturn(true);
-        when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo));
-        when(mPackageManager.getPackagesHoldingPermissions(
-                new String[]{android.Manifest.permission.MANAGE_USB},
-                PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(List.of(mPackageInfo));
-        UsbDevice device = Mockito.mock(UsbDevice.class);
-        mUsbProfileGroupSettingsManager.deviceAttached(device);
-        verify(mUsbUserSettingsManager, times(0))
-                .queryIntentActivities(any(Intent.class));
-    }
+    public void testDeviceAttached_restricUsbOverlayPropertyDisabled_resolveActivityCalled() {
+        when(mRestrictUsbOverlayActivitiesProperty.getBoolean()).thenReturn(false);
 
-    @Test
-    public void testDeviceAttached_foregroundActivityWithoutManifestField_resolveActivityCalled() {
-        when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true);
-        when(mProperty.getBoolean()).thenReturn(false);
-        when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo));
-        when(mPackageManager.getPackagesHoldingPermissions(
-                new String[]{android.Manifest.permission.MANAGE_USB},
-                PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(List.of(mPackageInfo));
-        UsbDevice device = Mockito.mock(UsbDevice.class);
-        mUsbProfileGroupSettingsManager.deviceAttached(device);
+        mUsbProfileGroupSettingsManager.deviceAttached(mUsbDevice);
+
         verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class));
     }
 
     @Test
-    public void testDeviceAttached_flagFalseForegroundActivity_resolveActivityCalled() {
+    public void testDeviceAttached_flagFalse_resolveActivityCalled() {
         when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(false);
-        when(mProperty.getBoolean()).thenReturn(true);
-        when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo));
-        when(mPackageManager.getPackagesHoldingPermissions(
-                new String[]{android.Manifest.permission.MANAGE_USB},
-                PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(List.of(mPackageInfo));
-        UsbDevice device = Mockito.mock(UsbDevice.class);
-        mUsbProfileGroupSettingsManager.deviceAttached(device);
+
+        mUsbProfileGroupSettingsManager.deviceAttached(mUsbDevice);
+
         verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class));
     }
+
+    @Test
+    public void
+            testDeviceAttached_setupNotCompleteAndNoBlockingActivities_resolveActivityNotCalled() {
+        when(mRestrictUsbOverlayActivitiesProperty.getBoolean()).thenReturn(false);
+        Settings.Secure.putInt(mContentResolver, USER_SETUP_COMPLETE, 0);
+
+        mUsbProfileGroupSettingsManager.deviceAttached(mUsbDevice);
+
+        verify(mUsbUserSettingsManager, times(0)).queryIntentActivities(any(Intent.class));
+    }
 }
diff --git a/tests/graphics/SilkFX/AndroidManifest.xml b/tests/graphics/SilkFX/AndroidManifest.xml
index c293589..25092b5 100644
--- a/tests/graphics/SilkFX/AndroidManifest.xml
+++ b/tests/graphics/SilkFX/AndroidManifest.xml
@@ -23,12 +23,13 @@
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
     <application android:label="SilkFX"
-         android:theme="@android:style/Theme.Material">
+         android:theme="@style/Theme.UsefulDefault">
 
         <activity android:name=".Main"
              android:label="SilkFX Demos"
              android:banner="@drawable/background1"
-             android:exported="true">
+             android:exported="true"
+             android:theme="@style/Theme.UsefulDefault">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.DEFAULT"/>
diff --git a/tests/graphics/SilkFX/res/values/style.xml b/tests/graphics/SilkFX/res/values/style.xml
index 66edbb5..4dd626d 100644
--- a/tests/graphics/SilkFX/res/values/style.xml
+++ b/tests/graphics/SilkFX/res/values/style.xml
@@ -23,9 +23,14 @@
         <item name="android:windowElevation">0dp</item>
         <item name="buttonStyle">@style/AppTheme.Button</item>
         <item name="colorAccent">#bbffffff</item>
+        <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
     </style>
     <style name="AppTheme.Button" parent="Widget.AppCompat.Button">
         <item name="android:textColor">#ffffffff</item>
     </style>
 
+    <style name="Theme.UsefulDefault" parent="android:Theme.Material">
+        <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
+    </style>
+
 </resources>
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
index e3f84c1..f126000 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
@@ -24,6 +24,7 @@
 
 import android.app.Activity;
 import android.os.Bundle;
+import android.os.Process;
 import android.util.Log;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
@@ -47,8 +48,9 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        Log.v(TAG, "Create MainActivity as user " + getUserId() + " on display "
-                + getDisplayId());
+        Log.v(TAG, "Create MainActivity as user "
+                + Process.myUserHandle().getIdentifier() + " on display "
+                + getDisplay().getDisplayId());
         setContentView(R.layout.main_activity);
         mImm = getSystemService(InputMethodManager.class);
         mEditor = requireViewById(R.id.edit_text);
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 960b57c..580efe1 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -70,6 +70,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.Uri;
+import android.net.vcn.Flags;
 import android.net.vcn.IVcnStatusCallback;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
 import android.net.vcn.VcnConfig;
@@ -82,7 +83,9 @@
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.test.TestLooper;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -101,6 +104,7 @@
 import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -118,6 +122,8 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class VcnManagementServiceTest {
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final String CONTEXT_ATTRIBUTION_TAG = "VCN";
     private static final String TEST_PACKAGE_NAME =
             VcnManagementServiceTest.class.getPackage().getName();
@@ -129,7 +135,12 @@
     private static final ParcelUuid TEST_UUID_3 = new ParcelUuid(new UUID(2, 2));
     private static final VcnConfig TEST_VCN_CONFIG;
     private static final VcnConfig TEST_VCN_CONFIG_PKG_2;
-    private static final int TEST_UID = Process.FIRST_APPLICATION_UID;
+
+    private static final int TEST_UID = 1010000; // A non-system user
+    private static final UserHandle TEST_USER_HANDLE = UserHandle.getUserHandleForUid(TEST_UID);
+    private static final UserHandle TEST_USER_HANDLE_OTHER =
+            UserHandle.of(TEST_USER_HANDLE.getIdentifier() + 1);
+
     private static final String TEST_IFACE_NAME = "TEST_IFACE";
     private static final String TEST_IFACE_NAME_2 = "TEST_IFACE2";
     private static final LinkProperties TEST_LP_1 = new LinkProperties();
@@ -187,6 +198,7 @@
     private final TelephonyManager mTelMgr = mock(TelephonyManager.class);
     private final SubscriptionManager mSubMgr = mock(SubscriptionManager.class);
     private final AppOpsManager mAppOpsMgr = mock(AppOpsManager.class);
+    private final UserManager mUserManager = mock(UserManager.class);
     private final VcnContext mVcnContext = mock(VcnContext.class);
     private final PersistableBundleUtils.LockingReadWriteHelper mConfigReadWriteHelper =
             mock(PersistableBundleUtils.LockingReadWriteHelper.class);
@@ -218,6 +230,9 @@
                 Context.TELEPHONY_SUBSCRIPTION_SERVICE,
                 SubscriptionManager.class);
         setupSystemService(mMockContext, mAppOpsMgr, Context.APP_OPS_SERVICE, AppOpsManager.class);
+        setupSystemService(mMockContext, mUserManager, Context.USER_SERVICE, UserManager.class);
+
+        doReturn(TEST_USER_HANDLE).when(mUserManager).getMainUser();
 
         doReturn(TEST_PACKAGE_NAME).when(mMockContext).getOpPackageName();
 
@@ -267,6 +282,8 @@
 
     @Before
     public void setUp() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_MAIN_USER);
+
         doNothing()
                 .when(mMockContext)
                 .enforceCallingOrSelfPermission(
@@ -717,10 +734,8 @@
     }
 
     @Test
-    public void testSetVcnConfigRequiresSystemUser() throws Exception {
-        doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, TEST_UID))
-                .when(mMockDeps)
-                .getBinderCallingUid();
+    public void testSetVcnConfigRequiresMainUser() throws Exception {
+        doReturn(TEST_USER_HANDLE_OTHER).when(mUserManager).getMainUser();
 
         try {
             mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
@@ -832,10 +847,8 @@
     }
 
     @Test
-    public void testClearVcnConfigRequiresSystemUser() throws Exception {
-        doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, TEST_UID))
-                .when(mMockDeps)
-                .getBinderCallingUid();
+    public void testClearVcnConfigRequiresMainUser() throws Exception {
+        doReturn(TEST_USER_HANDLE_OTHER).when(mUserManager).getMainUser();
 
         try {
             mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME);
@@ -921,10 +934,8 @@
     }
 
     @Test
-    public void testGetConfiguredSubscriptionGroupsRequiresSystemUser() throws Exception {
-        doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, TEST_UID))
-                .when(mMockDeps)
-                .getBinderCallingUid();
+    public void testGetConfiguredSubscriptionGroupsRequiresMainUser() throws Exception {
+        doReturn(TEST_USER_HANDLE_OTHER).when(mUserManager).getMainUser();
 
         try {
             mVcnMgmtSvc.getConfiguredSubscriptionGroups(TEST_PACKAGE_NAME);
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
index c8b60e5..441a4ae 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
@@ -20,6 +20,7 @@
 import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY;
 import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY;
 
+import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR;
 import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.MIN_VALID_EXPECTED_RX_PACKET_NUM;
 import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.getMaxSeqNumIncreasePerSecond;
 import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
@@ -584,4 +585,56 @@
                 MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED,
                 getMaxSeqNumIncreasePerSecond(mCarrierConfig));
     }
+
+    private IpSecPacketLossDetector newDetectorAndSetTransform(int threshold) throws Exception {
+        when(mCarrierConfig.getInt(
+                        eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY),
+                        anyInt()))
+                .thenReturn(threshold);
+
+        final IpSecPacketLossDetector detector =
+                new IpSecPacketLossDetector(
+                        mVcnContext,
+                        mNetwork,
+                        mCarrierConfig,
+                        mMetricMonitorCallback,
+                        mDependencies);
+
+        detector.setIsSelectedUnderlyingNetwork(true /* setIsSelected */);
+        detector.setInboundTransformInternal(mIpSecTransform);
+
+        return detector;
+    }
+
+    @Test
+    public void testDisableAndEnableDetectorWithCarrierConfig() throws Exception {
+        final IpSecPacketLossDetector detector =
+                newDetectorAndSetTransform(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR);
+
+        assertFalse(detector.isStarted());
+
+        when(mCarrierConfig.getInt(
+                        eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY),
+                        anyInt()))
+                .thenReturn(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD);
+        detector.setCarrierConfig(mCarrierConfig);
+
+        assertTrue(detector.isStarted());
+    }
+
+    @Test
+    public void testEnableAndDisableDetectorWithCarrierConfig() throws Exception {
+        final IpSecPacketLossDetector detector =
+                newDetectorAndSetTransform(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD);
+
+        assertTrue(detector.isStarted());
+
+        when(mCarrierConfig.getInt(
+                        eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY),
+                        anyInt()))
+                .thenReturn(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR);
+        detector.setCarrierConfig(mCarrierConfig);
+
+        assertFalse(detector.isStarted());
+    }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index edad678..0439d5f5 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -123,6 +123,7 @@
         mSetFlagsRule.enableFlags(Flags.FLAG_VALIDATE_NETWORK_ON_IPSEC_LOSS);
         mSetFlagsRule.enableFlags(Flags.FLAG_EVALUATE_IPSEC_LOSS_ON_LP_NC_CHANGE);
         mSetFlagsRule.enableFlags(Flags.FLAG_HANDLE_SEQ_NUM_LEAP);
+        mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_DISABLE_IPSEC_LOSS_DETECTOR);
 
         when(mNetwork.getNetId()).thenReturn(-1);
 
diff --git a/tools/aapt/Symbol.h b/tools/aapt/Symbol.h
index de1d60c..24c3208 100644
--- a/tools/aapt/Symbol.h
+++ b/tools/aapt/Symbol.h
@@ -40,7 +40,7 @@
 };
 
 /**
- * A specific defintion of a symbol, defined with a configuration and a definition site.
+ * A specific definition of a symbol, defined with a configuration and a definition site.
  */
 struct SymbolDefinition {
     inline SymbolDefinition();
@@ -92,4 +92,3 @@
 }
 
 #endif // AAPT_SYMBOL_H
-
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index f1e4ead..669cddb 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -443,7 +443,7 @@
   manifest_action.Action(AutoGenerateIsSplitRequired);
   manifest_action.Action(VerifyManifest);
   manifest_action.Action(FixCoreAppAttribute);
-  manifest_action.Action([&](xml::Element* el) -> bool {
+  manifest_action.Action([this, diag](xml::Element* el) -> bool {
     EnsureNamespaceIsDeclared("android", xml::kSchemaAndroid, &el->namespace_decls);
 
     if (options_.version_name_default) {
@@ -506,7 +506,7 @@
   manifest_action["eat-comment"];
 
   // Uses-sdk actions.
-  manifest_action["uses-sdk"].Action([&](xml::Element* el) -> bool {
+  manifest_action["uses-sdk"].Action([this](xml::Element* el) -> bool {
     if (options_.min_sdk_version_default &&
         el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion") == nullptr) {
       // There was no minSdkVersion defined and we have a default to assign.
@@ -528,7 +528,7 @@
 
   // Instrumentation actions.
   manifest_action["instrumentation"].Action(RequiredNameIsJavaClassName);
-  manifest_action["instrumentation"].Action([&](xml::Element* el) -> bool {
+  manifest_action["instrumentation"].Action([this](xml::Element* el) -> bool {
     if (!options_.rename_instrumentation_target_package) {
       return true;
     }
@@ -544,7 +544,7 @@
   manifest_action["attribution"];
   manifest_action["attribution"]["inherit-from"];
   manifest_action["original-package"];
-  manifest_action["overlay"].Action([&](xml::Element* el) -> bool {
+  manifest_action["overlay"].Action([this](xml::Element* el) -> bool {
     if (options_.rename_overlay_target_package) {
       if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "targetPackage")) {
         attr->value = options_.rename_overlay_target_package.value();
@@ -625,7 +625,7 @@
   uses_package_action["additional-certificate"];
 
   if (options_.debug_mode) {
-    application_action.Action([&](xml::Element* el) -> bool {
+    application_action.Action([](xml::Element* el) -> bool {
       xml::Attribute *attr = el->FindOrCreateAttribute(xml::kSchemaAndroid, "debuggable");
       attr->value = "true";
       return true;
diff --git a/tools/hoststubgen/TEST_MAPPING b/tools/hoststubgen/TEST_MAPPING
index f6885e1..856e6ee 100644
--- a/tools/hoststubgen/TEST_MAPPING
+++ b/tools/hoststubgen/TEST_MAPPING
@@ -1,63 +1,7 @@
-// Keep the following two TEST_MAPPINGs in sync:
-// frameworks/base/ravenwood/TEST_MAPPING
-// frameworks/base/tools/hoststubgen/TEST_MAPPING
 {
-  "presubmit": [
-    { "name": "tiny-framework-dump-test" },
-    { "name": "hoststubgentest" },
-    { "name": "hoststubgen-invoke-test" },
+  "imports": [
     {
-      "name": "RavenwoodMockitoTest_device"
-    },
-    {
-      "name": "RavenwoodBivalentTest_device"
-    },
-    // The sysui tests should match vendor/unbundled_google/packages/SystemUIGoogle/TEST_MAPPING
-    {
-      "name": "SystemUIGoogleTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
-    }
-  ],
-  "presubmit-large": [
-    {
-      "name": "SystemUITests",
-      "options": [
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
-    }
-  ],
-  "ravenwood-presubmit": [
-    {
-      "name": "RavenwoodMinimumTest",
-      "host": true
-    },
-    {
-      "name": "RavenwoodMockitoTest",
-      "host": true
-    },
-    {
-      "name": "CtsUtilTestCasesRavenwood",
-      "host": true
-    },
-    {
-      "name": "RavenwoodCoreTest",
-      "host": true
-    },
-    {
-      "name": "RavenwoodBivalentTest",
-      "host": true
+      "path": "frameworks/base/ravenwood"
     }
   ]
 }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
index 16785d1..6b360b7 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
@@ -25,6 +25,7 @@
         val aidlPolicy: FilterPolicyWithReason?,
         val featureFlagsPolicy: FilterPolicyWithReason?,
         val syspropsPolicy: FilterPolicyWithReason?,
+        val rFilePolicy: FilterPolicyWithReason?,
         fallback: OutputFilter
 ) : DelegatingFilter(fallback) {
     override fun getPolicyForClass(className: String): FilterPolicyWithReason {
@@ -37,6 +38,9 @@
         if (syspropsPolicy != null && classes.isSyspropsClass(className)) {
             return syspropsPolicy
         }
+        if (rFilePolicy != null && classes.isRClass(className)) {
+            return rFilePolicy
+        }
         return super.getPolicyForClass(className)
     }
 }
@@ -74,3 +78,10 @@
     return className.startsWith("android/sysprop/")
             && className.endsWith("Properties")
 }
+
+/**
+ * @return if a given class "seems like" an R class or its nested classes.
+ */
+private fun ClassNodes.isRClass(className: String): Boolean {
+    return className.endsWith("/R") || className.contains("/R$")
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
index 5659a35..2e144f5 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
@@ -15,11 +15,11 @@
  */
 package com.android.hoststubgen.filters
 
-import com.android.hoststubgen.UnknownApiException
 import com.android.hoststubgen.addNonNullElement
 import com.android.hoststubgen.asm.ClassNodes
 import com.android.hoststubgen.asm.toHumanReadableClassName
 import com.android.hoststubgen.asm.toHumanReadableMethodName
+import com.android.hoststubgen.log
 
 // TODO: Validate all input names.
 
@@ -48,30 +48,30 @@
         return mPolicies[getClassKey(className)] ?: super.getPolicyForClass(className)
     }
 
-    private fun ensureClassExists(className: String) {
+    private fun checkClass(className: String) {
         if (classes.findClass(className) == null) {
-            throw UnknownApiException("Unknown class $className")
+            log.w("Unknown class $className")
         }
     }
 
-    private fun ensureFieldExists(className: String, fieldName: String) {
+    private fun checkField(className: String, fieldName: String) {
         if (classes.findField(className, fieldName) == null) {
-            throw UnknownApiException("Unknown field $className.$fieldName")
+            log.w("Unknown field $className.$fieldName")
         }
     }
 
-    private fun ensureMethodExists(
+    private fun checkMethod(
         className: String,
         methodName: String,
         descriptor: String
     ) {
         if (classes.findMethod(className, methodName, descriptor) == null) {
-            throw UnknownApiException("Unknown method $className.$methodName$descriptor")
+            log.w("Unknown method $className.$methodName$descriptor")
         }
     }
 
     fun setPolicyForClass(className: String, policy: FilterPolicyWithReason) {
-        ensureClassExists(className)
+        checkClass(className)
         mPolicies[getClassKey(className)] = policy
     }
 
@@ -81,7 +81,7 @@
     }
 
     fun setPolicyForField(className: String, fieldName: String, policy: FilterPolicyWithReason) {
-        ensureFieldExists(className, fieldName)
+        checkField(className, fieldName)
         mPolicies[getFieldKey(className, fieldName)] = policy
     }
 
@@ -100,7 +100,7 @@
             descriptor: String,
             policy: FilterPolicyWithReason,
             ) {
-        ensureMethodExists(className, methodName, descriptor)
+        checkMethod(className, methodName, descriptor)
         mPolicies[getMethodKey(className, methodName, descriptor)] = policy
     }
 
@@ -110,8 +110,8 @@
     }
 
     fun setRenameTo(className: String, methodName: String, descriptor: String, toName: String) {
-        ensureMethodExists(className, methodName, descriptor)
-        ensureMethodExists(className, toName, descriptor)
+        checkMethod(className, methodName, descriptor)
+        checkMethod(className, toName, descriptor)
         mRenames[getMethodKey(className, methodName, descriptor)] = toName
     }
 
@@ -121,7 +121,7 @@
     }
 
     fun setNativeSubstitutionClass(from: String, to: String) {
-        ensureClassExists(from)
+        checkClass(from)
 
         // Native substitute classes may be provided from other jars, so we can't do this check.
         // ensureClassExists(to)
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index 75b5fc8..c5acd81 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -17,6 +17,7 @@
 
 import com.android.hoststubgen.ParseException
 import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.toHumanReadableClassName
 import com.android.hoststubgen.log
 import com.android.hoststubgen.normalizeTextLine
 import com.android.hoststubgen.whitespaceRegex
@@ -31,13 +32,17 @@
  * Print a class node as a "keep" policy.
  */
 fun printAsTextPolicy(pw: PrintWriter, cn: ClassNode) {
-    pw.printf("class %s\t%s\n", cn.name, "keep")
+    pw.printf("class %s %s\n", cn.name.toHumanReadableClassName(), "keep")
 
-    for (f in cn.fields ?: emptyList()) {
-        pw.printf("  field %s\t%s\n", f.name, "keep")
+    cn.fields?.let {
+        for (f in it.sortedWith(compareBy({ it.name }))) {
+            pw.printf("    field %s %s\n", f.name, "keep")
+        }
     }
-    for (m in cn.methods ?: emptyList()) {
-        pw.printf("  method %s\t%s\t%s\n", m.name, m.desc, "keep")
+    cn.methods?.let {
+        for (m in it.sortedWith(compareBy({ it.name }, { it.desc }))) {
+            pw.printf("    method %s %s %s\n", m.name, m.desc, "keep")
+        }
     }
 }
 
@@ -66,6 +71,7 @@
         var aidlPolicy: FilterPolicyWithReason? = null
         var featureFlagsPolicy: FilterPolicyWithReason? = null
         var syspropsPolicy: FilterPolicyWithReason? = null
+        var rFilePolicy: FilterPolicyWithReason? = null
 
         try {
             BufferedReader(FileReader(filename)).use { reader ->
@@ -162,6 +168,14 @@
                                         syspropsPolicy = policy.withReason(
                                                 "$FILTER_REASON (special-class sysprops)")
                                     }
+                                    SpecialClass.RFile -> {
+                                        if (rFilePolicy != null) {
+                                            throw ParseException(
+                                                "Policy for R file already defined")
+                                        }
+                                        rFilePolicy = policy.withReason(
+                                            "$FILTER_REASON (special-class R file)")
+                                    }
                                 }
                             }
                         }
@@ -225,13 +239,9 @@
             throw e.withSourceInfo(filename, lineNo)
         }
 
-        var ret: OutputFilter = imf
-        if (aidlPolicy != null || featureFlagsPolicy != null || syspropsPolicy != null) {
-            log.d("AndroidHeuristicsFilter enabled")
-            ret = AndroidHeuristicsFilter(
-                    classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, imf)
-        }
-        return ret
+        // Wrap the in-memory-filter with AHF.
+        return AndroidHeuristicsFilter(
+                classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, rFilePolicy, imf)
     }
 }
 
@@ -240,6 +250,7 @@
     Aidl,
     FeatureFlags,
     Sysprops,
+    RFile,
 }
 
 private fun resolveSpecialClass(className: String): SpecialClass {
@@ -250,6 +261,7 @@
         ":aidl" -> return SpecialClass.Aidl
         ":feature_flags" -> return SpecialClass.FeatureFlags
         ":sysprops" -> return SpecialClass.Sysprops
+        ":r" -> return SpecialClass.RFile
     }
     throw ParseException("Invalid special class name \"$className\"")
 }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index fa8fe6c..931f0c5 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -322,6 +322,78 @@
 InnerClasses:
   public static #x= #x of #x;            // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
   public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class
+  Compiled from "R.java"
+public class com.android.hoststubgen.test.tinyframework.R$Nested
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/R$Nested
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 3
+  public static int[] ARRAY;
+    descriptor: [I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+
+  public com.android.hoststubgen.test.tinyframework.R$Nested();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/R$Nested;
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: iconst_1
+         x: newarray       int
+         x: dup
+         x: iconst_0
+         x: iconst_1
+         x: iastore
+         x: putstatic     #x                  // Field ARRAY:[I
+        x: return
+      LineNumberTable:
+}
+SourceFile: "R.java"
+NestHost: class com/android/hoststubgen/test/tinyframework/R
+InnerClasses:
+  public static #x= #x of #x;           // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+## Class: com/android/hoststubgen/test/tinyframework/R.class
+  Compiled from "R.java"
+public class com.android.hoststubgen.test.tinyframework.R
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/R
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 3
+  public com.android.hoststubgen.test.tinyframework.R();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/R;
+}
+SourceFile: "R.java"
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/R$Nested
+InnerClasses:
+  public static #x= #x of #x;           // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
   Compiled from "TinyFrameworkCallerCheck.java"
 class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
index c605f76..906a81c 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
@@ -122,6 +122,100 @@
 NestMembers:
   com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
   com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
+## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class
+  Compiled from "R.java"
+public class com.android.hoststubgen.test.tinyframework.R$Nested
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/R$Nested
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 4
+  public static int[] ARRAY;
+    descriptor: [I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public com.android.hoststubgen.test.tinyframework.R$Nested();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+  public static #x= #x of #x;            // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+SourceFile: "R.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestHost: class com/android/hoststubgen/test/tinyframework/R
+## Class: com/android/hoststubgen/test/tinyframework/R.class
+  Compiled from "R.java"
+public class com.android.hoststubgen.test.tinyframework.R
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/R
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 4
+  public com.android.hoststubgen.test.tinyframework.R();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+  public static #x= #x of #x;            // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+SourceFile: "R.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/R$Nested
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
   Compiled from "TinyFrameworkCallerCheck.java"
 class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
index 11d5939..10bc91d 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
@@ -348,6 +348,108 @@
 NestMembers:
   com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
   com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
+## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class
+  Compiled from "R.java"
+public class com.android.hoststubgen.test.tinyframework.R$Nested
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/R$Nested
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 4
+  public static int[] ARRAY;
+    descriptor: [I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public com.android.hoststubgen.test.tinyframework.R$Nested();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/R$Nested;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: iconst_1
+         x: newarray       int
+         x: dup
+         x: iconst_0
+         x: iconst_1
+         x: iastore
+         x: putstatic     #x                 // Field ARRAY:[I
+        x: return
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+  public static #x= #x of #x;            // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+SourceFile: "R.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestHost: class com/android/hoststubgen/test/tinyframework/R
+## Class: com/android/hoststubgen/test/tinyframework/R.class
+  Compiled from "R.java"
+public class com.android.hoststubgen.test.tinyframework.R
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/R
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 4
+  public com.android.hoststubgen.test.tinyframework.R();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/R;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+  public static #x= #x of #x;            // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+SourceFile: "R.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/R$Nested
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
   Compiled from "TinyFrameworkCallerCheck.java"
 class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
index c605f76..906a81c 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
@@ -122,6 +122,100 @@
 NestMembers:
   com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
   com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
+## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class
+  Compiled from "R.java"
+public class com.android.hoststubgen.test.tinyframework.R$Nested
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/R$Nested
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 4
+  public static int[] ARRAY;
+    descriptor: [I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public com.android.hoststubgen.test.tinyframework.R$Nested();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+  public static #x= #x of #x;            // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+SourceFile: "R.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestHost: class com/android/hoststubgen/test/tinyframework/R
+## Class: com/android/hoststubgen/test/tinyframework/R.class
+  Compiled from "R.java"
+public class com.android.hoststubgen.test.tinyframework.R
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/R
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 4
+  public com.android.hoststubgen.test.tinyframework.R();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+  public static #x= #x of #x;            // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+SourceFile: "R.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/R$Nested
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
   Compiled from "TinyFrameworkCallerCheck.java"
 class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
index 088bc80..fcf9a8c 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
@@ -481,6 +481,136 @@
 NestMembers:
   com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
   com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
+## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class
+  Compiled from "R.java"
+public class com.android.hoststubgen.test.tinyframework.R$Nested
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/R$Nested
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 4
+  public static int[] ARRAY;
+    descriptor: [I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public com.android.hoststubgen.test.tinyframework.R$Nested();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/R$Nested
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/R$Nested;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/R$Nested
+         x: ldc           #x                 // String <clinit>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/R$Nested
+        x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+        x: iconst_1
+        x: newarray       int
+        x: dup
+        x: iconst_0
+        x: iconst_1
+        x: iastore
+        x: putstatic     #x                 // Field ARRAY:[I
+        x: return
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+  public static #x= #x of #x;            // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+SourceFile: "R.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestHost: class com/android/hoststubgen/test/tinyframework/R
+## Class: com/android/hoststubgen/test/tinyframework/R.class
+  Compiled from "R.java"
+public class com.android.hoststubgen.test.tinyframework.R
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/R
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 4
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/R
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+
+  public com.android.hoststubgen.test.tinyframework.R();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/R
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/R;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+  public static #x= #x of #x;           // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+SourceFile: "R.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/R$Nested
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
   Compiled from "TinyFrameworkCallerCheck.java"
 class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
index d302084..696b6d0 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
@@ -19,6 +19,9 @@
 # Heuristics rule: Stub all the AIDL classes.
 class :aidl stubclass
 
+# Heuristics rule: Stub all the R classes.
+class :r stubclass
+
 # Default is "remove", so let's put all the base classes / interfaces in the stub first.
 class com.android.hoststubgen.test.tinyframework.subclasstest.C1 stub
 class com.android.hoststubgen.test.tinyframework.subclasstest.C2 stub
diff --git a/core/java/android/app/StatusBarManager.aidl b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/R.java
similarity index 64%
rename from core/java/android/app/StatusBarManager.aidl
rename to tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/R.java
index 687678c..b1bedf4 100644
--- a/core/java/android/app/StatusBarManager.aidl
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/R.java
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2024, 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.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *      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,
@@ -13,7 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.hoststubgen.test.tinyframework;
 
-package android.app;
-
-parcelable StatusBarManager.DisableInfo;
\ No newline at end of file
+public class R {
+    public static class Nested {
+        public static int[] ARRAY = new int[] {1};
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index 762180d..37925e8 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -19,6 +19,7 @@
 
 import static org.junit.Assert.fail;
 
+import com.android.hoststubgen.test.tinyframework.R.Nested;
 import com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.SubClass;
 
 import org.junit.Rule;
@@ -328,4 +329,9 @@
         assertThat(IPretendingAidl.Stub.addOne(1)).isEqualTo(2);
         assertThat(IPretendingAidl.Stub.Proxy.addTwo(1)).isEqualTo(3);
     }
+
+    @Test
+    public void testRFileHeuristics() {
+        assertThat(Nested.ARRAY.length).isEqualTo(1);
+    }
 }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
index 3d5a0f7..395f744 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
@@ -21,6 +21,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.net.wifi.flags.Flags;
 import android.net.wifi.sharedconnectivity.service.SharedConnectivityService;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -170,7 +171,7 @@
          * @return Returns the Builder object.
          */
         @NonNull
-        @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status")
+        @FlaggedApi(Flags.FLAG_NETWORK_PROVIDER_BATTERY_CHARGING_STATUS)
         public Builder setBatteryCharging(boolean isBatteryCharging) {
             mIsBatteryCharging = isBatteryCharging;
             return this;
@@ -285,7 +286,7 @@
      *
      * @return Returns true if the battery of the remote device is charging.
      */
-    @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status")
+    @FlaggedApi(Flags.FLAG_NETWORK_PROVIDER_BATTERY_CHARGING_STATUS)
     public boolean isBatteryCharging() {
         return mIsBatteryCharging;
     }
diff --git a/wifi/wifi.aconfig b/wifi/wifi.aconfig
index 3c734bc..c5bc039 100644
--- a/wifi/wifi.aconfig
+++ b/wifi/wifi.aconfig
@@ -9,3 +9,11 @@
     bug: "313038031"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "network_provider_battery_charging_status"
+    is_exported: true
+    namespace: "wifi"
+    description: "Control the API that allows setting / reading the NetworkProviderInfo's battery charging status"
+    bug: "305067231"
+}